From 3577f495cc593a358d0910ab16b8607600066b52 Mon Sep 17 00:00:00 2001 From: Natan Abramov Date: Wed, 19 Mar 2025 13:06:08 +0200 Subject: [PATCH 01/44] Migrate valuad prebid.js to 9.35.0 (bridgeupp requires 9.24+) --- .gitignore | 3 +++ modules.json | 42 ++++++++++++++++++++++++++++++++++++++++++ package.json | 3 ++- transform.js | 17 +++++++++++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 modules.json create mode 100644 transform.js diff --git a/.gitignore b/.gitignore index e5f000dd4d5..07380a0e73d 100644 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,6 @@ typings/ # MacOS system files .DS_Store + +/prebid.js +/prebid.js.gz diff --git a/modules.json b/modules.json new file mode 100644 index 00000000000..31d8c64424e --- /dev/null +++ b/modules.json @@ -0,0 +1,42 @@ +[ + "priceFloors", + "currency", + "tcfControl", + "consentManagementTcf", + "consentManagementUsp", + "schain", + "userId", + "33acrossIdSystem", + "criteoIdSystem", + "gptPreAuction", + "paapi", + "paapiForGpt", + "rtdModule", + "topicsFpdModule", + "sovrnBidAdapter", + "appnexusBidAdapter", + "conversantBidAdapter", + "33acrossBidAdapter", + "openxBidAdapter", + "pubmaticBidAdapter", + "adyoulikeBidAdapter", + "criteoBidAdapter", + "outbrainBidAdapter", + "onetagBidAdapter", + "adagioBidAdapter", + "adagioRtdProvider", + "tripleliftBidAdapter", + "smartadserverBidAdapter", + "rubiconBidAdapter", + "yandexBidAdapter", + "rtbhouseBidAdapter", + "teadsBidAdapter", + "omsBidAdapter", + "nobidBidAdapter", + "riseBidAdapter", + "copper6sspBidAdapter", + "taboolaBidAdapter", + "prebidServerBidAdapter", + "s2sTesting", + "bridgeuppBidAdapter" +] diff --git a/package.json b/package.json index 8567100cc95..bb4c4ba3624 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "./modules/*.js": "./modules/*.js" }, "scripts": { + "start": "gulp build --modules=modules.json && cp ./build/dist/prebid.js ../prebid.js && node transform.js", "serve": "gulp serve", "test": "gulp test", "lint": "gulp lint" @@ -37,7 +38,7 @@ "header bidding", "prebid" ], - "globalVarName": "pbjs", + "globalVarName": "vadprebid", "defineGlobal": true, "author": "The prebid.js contributors", "license": "Apache-2.0", diff --git a/transform.js b/transform.js new file mode 100644 index 00000000000..e98e899861a --- /dev/null +++ b/transform.js @@ -0,0 +1,17 @@ +const path = require('path'); +const fs = require('fs').promises; + +(async () => { + const filePath = path.join(__dirname, 'prebid.js'); + let pb = await fs.readFile(filePath, 'utf8'); + const regex = /url:\(void 0!==t\?t:"https:\/\/prg\.smartadserver\.com"\)\+"\/prebid\/v1"/ + if(regex.test(pb)) { + console.log('replacing smartadserver domain to hardcoded'); + pb = pb.replace(regex, 'url:"https://prg.smartadserver.com/prebid/v1"'); + } else { + await fs.unlink(filePath); + throw new Error('smartadserver domain not replaced') + } + await fs.writeFile(filePath, pb, 'utf8'); + console.log('done'); +})(); From 44409ef221f23bea6291343227835eb4022a723f Mon Sep 17 00:00:00 2001 From: Natan Abramov Date: Wed, 19 Mar 2025 13:16:35 +0200 Subject: [PATCH 02/44] Fix s2s config ad server currency --- modules/prebidServerBidAdapter/ortbConverter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 99fbcf3d2bb..ef82fcdfd50 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -322,7 +322,7 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste bidderRequest: proxyBidderRequest, bidRequests: proxyBidRequests, context: { - currency: config.getConfig('currency.adServerCurrency') || DEFAULT_S2S_CURRENCY, + currency: s2sBidRequest.s2sConfig.adServerCurrency || config.getConfig('currency.adServerCurrency') || DEFAULT_S2S_CURRENCY, ttl: s2sBidRequest.s2sConfig.defaultTtl || DEFAULT_S2S_TTL, requestTimestamp, s2sBidRequest: { From cf9479f240f32e208a581fc96f18833f4035b336 Mon Sep 17 00:00:00 2001 From: Natan Abramov Date: Thu, 20 Mar 2025 13:34:26 +0200 Subject: [PATCH 03/44] Add valuadBidAdapter with all enhancements from 9.14.0 --- modules.json | 3 +- modules/valuadBidAdapter.js | 324 ++++++++++++++++++++++++++++++++++++ modules/valuadBidAdapter.md | 30 ++++ 3 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 modules/valuadBidAdapter.js create mode 100644 modules/valuadBidAdapter.md diff --git a/modules.json b/modules.json index 31d8c64424e..1656e7f0d73 100644 --- a/modules.json +++ b/modules.json @@ -38,5 +38,6 @@ "taboolaBidAdapter", "prebidServerBidAdapter", "s2sTesting", - "bridgeuppBidAdapter" + "bridgeuppBidAdapter", + "valuadBidAdapter" ] diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js new file mode 100644 index 00000000000..8237b7950b0 --- /dev/null +++ b/modules/valuadBidAdapter.js @@ -0,0 +1,324 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { + deepSetValue, + generateUUID, + getWindowSelf, + getWindowTop, + canAccessWindowTop, + getDNT, + logInfo, +} from '../src/utils.js'; +import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; +import { config } from '../src/config.js'; +import { parseDomain } from '../src/refererDetection.js'; + +const BIDDER_CODE = 'valuad'; +const AD_URL = 'https://valuad-server-test.appspot.com/adapter'; +const SYNC_URL = 'https://valuad-server-test.appspot.com/cookie_sync'; + +export const _VALUAD = (function() { + const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); + + w.VALUAD = w.VALUAD || {}; + w.VALUAD.pageviewId = w.VALUAD.pageviewId || generateUUID(); + w.VALUAD.sessionId = w.VALUAD.sessionId || generateUUID(); + w.VALUAD.sessionStartTime = w.VALUAD.sessionStartTime || Date.now(); + w.VALUAD.pageLoadTime = w.VALUAD.pageLoadTime || window.performance?.timing?.domContentLoadedEventEnd - window.performance?.timing?.navigationStart; + w.VALUAD.userActivity = w.VALUAD.userActivity || { + lastActivityTime: Date.now(), + pageviewCount: (w.VALUAD.userActivity?.pageviewCount || 0) + 1 + }; + + return w.VALUAD; +})(); + +// Helper functions to enrich data +function getDevice() { + const language = navigator.language ? 'language' : 'userLanguage'; + const deviceInfo = { + userAgent: navigator.userAgent, + language: navigator[language], + dnt: getDNT() ? 1 : 0, + js: 1, + geo: {} + }; + + // Get screen dimensions + if (window.screen) { + deviceInfo.w = window.screen.width; + deviceInfo.h = window.screen.height; + } + + // Get viewport dimensions + deviceInfo.ext = { + vpw: window.innerWidth, + vph: window.innerHeight + }; + + return deviceInfo; +} + +function getSite(bidderRequest) { + const { refererInfo } = bidderRequest; + const siteInfo = { + domain: parseDomain(refererInfo.topmostLocation) || '', + page: refererInfo.topmostLocation || '', + referrer: refererInfo.ref || getWindowSelf().document.referrer || '', + top: refererInfo.reachedTop + }; + + // Add page metadata if available + const meta = document.querySelector('meta[name="keywords"]'); + if (meta && meta.content) { + siteInfo.keywords = meta.content; + } + + return siteInfo; +} + +function getSession() { + return { + id: _VALUAD.sessionId, + startTime: _VALUAD.sessionStartTime, + lastActivityTime: _VALUAD.userActivity.lastActivityTime, + pageviewCount: _VALUAD.userActivity.pageviewCount, + pageLoadTime: _VALUAD.pageLoadTime || 0, + new: _VALUAD.userActivity.pageviewCount === 1 + }; +} + +// Add detailed ad unit position detection +function detectAdUnitPosition(adUnitCode) { + const element = document.getElementById(adUnitCode) || document.getElementById(getGptSlotInfoForAdUnitCode(adUnitCode)?.divId); + if (!element) return null; + + const rect = element.getBoundingClientRect(); + const docElement = document.documentElement; + const pageWidth = docElement.clientWidth; + const pageHeight = docElement.scrollHeight; + + return { + x: Math.round(rect.left + window.pageXOffset), + y: Math.round(rect.top + window.pageYOffset), + w: Math.round(rect.width), + h: Math.round(rect.height), + position: `${Math.round(rect.left + window.pageXOffset)}x${Math.round(rect.top + window.pageYOffset)}`, + viewportVisibility: calculateVisibility(rect), + pageSize: `${pageWidth}x${pageHeight}` + }; +} + +function calculateVisibility(rect) { + const windowHeight = window.innerHeight; + const windowWidth = window.innerWidth; + + // Element is not in viewport + if (rect.bottom < 0 || rect.right < 0 || rect.top > windowHeight || rect.left > windowWidth) { + return 0; + } + + // Calculate visible area + const visibleHeight = Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0); + const visibleWidth = Math.min(rect.right, windowWidth) - Math.max(rect.left, 0); + const visibleArea = visibleHeight * visibleWidth; + const totalArea = rect.height * rect.width; + + return totalArea > 0 ? visibleArea / totalArea : 0; +} + +// Enhanced ORTBConverter with additional data +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 30 + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + const device = getDevice(); + const site = getSite(bidderRequest); + const session = getSession(); + + // Ensure we have required extensions + deepSetValue(request, 'device', {...request.device, ...device}); + deepSetValue(request, 'site', {...request.site, ...site}); + + deepSetValue(request, 'site.ext.data.valuad_rtd', { + pageviewId: _VALUAD.pageviewId, + session: session, + features: { + page_dimensions: `${document.documentElement.scrollWidth}x${document.documentElement.scrollHeight}`, + viewport_dimensions: `${window.innerWidth}x${window.innerHeight}`, + user_timestamp: Math.floor(Date.now() / 1000), + dom_loading: window.performance?.timing?.domContentLoadedEventEnd - window.performance?.timing?.navigationStart + } + }); + + // Add bid parameters + if (bidderRequest && bidderRequest.bids && bidderRequest.bids.length) { + deepSetValue(request, 'ext.params', bidderRequest.bids[0].params); + } + + // Set currency to USD + deepSetValue(request, 'cur', ['USD']); + + return request; + }, + + imp(buildImp, bid, context) { + const imp = buildImp(bid, context); + + // Add additional impression data + const positionData = detectAdUnitPosition(bid.adUnitCode); + if (positionData) { + deepSetValue(imp, 'ext.data.adg_rtd.adunit_position', positionData.position); + deepSetValue(imp, 'ext.data.viewability', positionData.viewportVisibility); + } + + // GPT information + const gptInfo = getGptSlotInfoForAdUnitCode(bid.adUnitCode); + if (gptInfo) { + deepSetValue(imp, 'ext.data.adserver', { + name: 'gam', + adslot: gptInfo.gptSlot + }); + deepSetValue(imp, 'ext.data.pbadslot', gptInfo.gptSlot); + + // If not already set, add gpid + if (!imp.ext.gpid && gptInfo.gptSlot) { + deepSetValue(imp, 'ext.gpid', gptInfo.gptSlot); + } + } + + // Handle price floors + if (typeof bid.getFloor === 'function') { + try { + const mediaType = Object.keys(bid.mediaTypes)[0]; + let size; + + if (mediaType === BANNER) { + size = bid.mediaTypes.banner.sizes && bid.mediaTypes.banner.sizes[0]; + } else if (mediaType === VIDEO) { + size = bid.mediaTypes.video.playerSize; + } + + if (size) { + const floor = bid.getFloor({ + currency: 'USD', + mediaType, + size + }); + + if (floor && !isNaN(floor.floor) && floor.currency === 'USD') { + imp.bidfloor = floor.floor; + imp.bidfloorcur = 'USD'; + } + } + } catch (e) { + logInfo('Valuad: Error getting floor', e); + } + } + + return imp; + } +}); + +const isBidRequestValid = () => (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + + const foundKeys = bid && bid.params && bid.params.placementId; + let valid = Boolean(bidId && params && foundKeys); + + 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; +}; + +const buildRequests = (adUrl) => (validBidRequests = [], bidderRequest = {}) => { + // Add bid-level metadata for our server to use + validBidRequests = validBidRequests.map(req => { + req.valuadMeta = { + pageviewId: _VALUAD.pageviewId, + adUnitPosition: detectAdUnitPosition(req.adUnitCode) + }; + return req; + }); + + const data = converter.toORTB({ validBidRequests, bidderRequest }); + + // Update session data + _VALUAD.userActivity.lastActivityTime = Date.now(); + + return [{ + method: 'POST', + url: adUrl, + data + }]; +}; + +const interpretResponse = () => (response, request) => { + const bidResponses = converter.fromORTB({response: response.body, request: request.data}).bids; + + // Process server-side data + if (response.body && response.body.ext && response.body.ext.valuad_data) { + // Store any server-side enhanced data for future use + _VALUAD.serverData = response.body.ext.valuad_data; + } + + return bidResponses; +}; + +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}`; + + // Add our session ID + url += `&sid=${_VALUAD.sessionId}`; + + return [{ + type, + url + }]; +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + gvlid: 1234, // Replace with your actual GVL ID for GDPR purposes + + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse: interpretResponse(), + getUserSyncs: getUserSyncs(SYNC_URL) +}; + +registerBidder(spec); diff --git a/modules/valuadBidAdapter.md b/modules/valuadBidAdapter.md new file mode 100644 index 00000000000..9de018e98e7 --- /dev/null +++ b/modules/valuadBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +**Module Name**: Valuad Bid Adapter +**Module Type**: Bidder Adapter +**Maintainer**: natan@valuad.io + +# Description + + +Module that connects to Valuad.io demand sources. +Valuad bid adapter supports Banner format only. + +# Test Parameters + +```js + const adUnits = [{ + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'valuad', + params: { + placementId: 'test', // REQUIRED + } + }] + }]; +``` From 4b20d4c80085bfb49f8b247a18046507f3c2abe9 Mon Sep 17 00:00:00 2001 From: natanavra Date: Sun, 23 Mar 2025 10:42:14 +0200 Subject: [PATCH 04/44] Remove rise adapter --- modules.json | 1 - 1 file changed, 1 deletion(-) diff --git a/modules.json b/modules.json index 1656e7f0d73..20ae59b0d6d 100644 --- a/modules.json +++ b/modules.json @@ -33,7 +33,6 @@ "teadsBidAdapter", "omsBidAdapter", "nobidBidAdapter", - "riseBidAdapter", "copper6sspBidAdapter", "taboolaBidAdapter", "prebidServerBidAdapter", From 043d9495e387a7cb5ff676f3218c2f5903d2b981 Mon Sep 17 00:00:00 2001 From: natanavra Date: Sun, 23 Mar 2025 10:42:32 +0200 Subject: [PATCH 05/44] Remove s2s adapters in favor of valuad bid adapter --- modules.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules.json b/modules.json index 20ae59b0d6d..05bfcae4dbb 100644 --- a/modules.json +++ b/modules.json @@ -35,8 +35,6 @@ "nobidBidAdapter", "copper6sspBidAdapter", "taboolaBidAdapter", - "prebidServerBidAdapter", - "s2sTesting", "bridgeuppBidAdapter", "valuadBidAdapter" ] From 6f59f663e42664fec06686d718a878ac65d4b544 Mon Sep 17 00:00:00 2001 From: natanavra Date: Sun, 23 Mar 2025 17:46:09 +0200 Subject: [PATCH 06/44] Import 9.14.0 changes --- modules/valuadBidAdapter.js | 85 ++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index 8237b7950b0..7910b70ab6a 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -2,6 +2,8 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { + cleanObj, + deepAccess, deepSetValue, generateUUID, getWindowSelf, @@ -128,6 +130,35 @@ function calculateVisibility(rect) { return totalArea > 0 ? visibleArea / totalArea : 0; } +function getGdprConsent(bidderRequest) { + if (!deepAccess(bidderRequest, 'gdprConsent')) { + return false; + } + + const { + apiVersion, + gdprApplies, + consentString, + allowAuctionWithoutConsent + } = bidderRequest.gdprConsent; + + return cleanObj({ + apiVersion, + consentString, + consentRequired: gdprApplies ? 1 : 0, + allowAuctionWithoutConsent: allowAuctionWithoutConsent ? 1 : 0 + }); +} + +function getCoppa() { + return { + required: config.getConfig('coppa') === true ? 1 : 0 + }; +} + +function getUspConsent(bidderRequest) { + return (deepAccess(bidderRequest, 'uspConsent')) ? { uspConsent: bidderRequest.uspConsent } : false; +} // Enhanced ORTBConverter with additional data const converter = ortbConverter({ context: { @@ -140,10 +171,25 @@ const converter = ortbConverter({ const site = getSite(bidderRequest); const session = getSession(); + const gdprConsent = getGdprConsent(bidderRequest) || {}; + const uspConsent = getUspConsent(bidderRequest) || {}; + const coppa = getCoppa(); + const { gpp, gpp_sid: gppSid } = deepAccess(bidderRequest, 'ortb2.regs', {}); + const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); + // Ensure we have required extensions deepSetValue(request, 'device', {...request.device, ...device}); deepSetValue(request, 'site', {...request.site, ...site}); + deepSetValue(request, 'regs', { + gdpr: gdprConsent, + coppa: coppa, + ccpa: uspConsent, + gpp: gpp || '', + gppSid: gppSid || [], + dsa: dsa + }); + deepSetValue(request, 'site.ext.data.valuad_rtd', { pageviewId: _VALUAD.pageviewId, session: session, @@ -277,48 +323,27 @@ const interpretResponse = () => (response, request) => { return bidResponses; }; -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}`; - } +const getUserSyncs = () => (syncOptions, serverResponses) => { + if (!serverResponses.length || serverResponses[0].body === '' || !serverResponses[0].body.userSyncs) { + return false; } - if (uspConsent && uspConsent.consentString) { - url += `&ccpa_consent=${uspConsent.consentString}`; - } + const syncs = serverResponses[0].body.userSyncs.map(sync => ({ + type: sync.type === 'iframe' ? 'iframe' : 'image', + url: sync.url + })); - 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}`; - - // Add our session ID - url += `&sid=${_VALUAD.sessionId}`; - - return [{ - type, - url - }]; + return syncs; }; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - gvlid: 1234, // Replace with your actual GVL ID for GDPR purposes isBidRequestValid: isBidRequestValid(), buildRequests: buildRequests(AD_URL), interpretResponse: interpretResponse(), - getUserSyncs: getUserSyncs(SYNC_URL) + getUserSyncs: getUserSyncs() }; registerBidder(spec); From c1472edd6e14d482851e599731acbe996deb0f27 Mon Sep 17 00:00:00 2001 From: natanavra Date: Sun, 23 Mar 2025 17:51:33 +0200 Subject: [PATCH 07/44] Revert smart and yandex adServerCurrency wrong setting --- modules/smartadserverBidAdapter.js | 2 +- modules/yandexBidAdapter.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index e3cdb3dc5bf..a6ef9d4d0b3 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -181,7 +181,7 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { // use bidderRequest.bids[] to get bidder-dependent request info - const adServerCurrency = getCurrencyFromBidderRequest(bidderRequest); + const adServerCurrency = config.getConfig('currency.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/yandexBidAdapter.js b/modules/yandexBidAdapter.js index eed0902e9dc..af0aab2bcc4 100644 --- a/modules/yandexBidAdapter.js +++ b/modules/yandexBidAdapter.js @@ -1,4 +1,5 @@ import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; +import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; @@ -128,7 +129,7 @@ export const spec = { timeout = bidderRequest.timeout; } - const adServerCurrency = getCurrencyFromBidderRequest(bidderRequest); + const adServerCurrency = config.getConfig('currency.adServerCurrency') || getCurrencyFromBidderRequest(bidderRequest); return validBidRequests.map((bidRequest) => { const { params } = bidRequest; From 289bdc2c4376276416e89e3c9e351df547cfac5a Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Mon, 24 Mar 2025 13:05:12 +0200 Subject: [PATCH 08/44] Added support for onBidWon + fix for Regs --- modules/valuadBidAdapter.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index 7910b70ab6a..0b32e418329 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -15,10 +15,11 @@ import { import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; import { config } from '../src/config.js'; import { parseDomain } from '../src/refererDetection.js'; +import { triggerPixel } from '../src/utils.js'; const BIDDER_CODE = 'valuad'; const AD_URL = 'https://valuad-server-test.appspot.com/adapter'; -const SYNC_URL = 'https://valuad-server-test.appspot.com/cookie_sync'; +const WON_URL = 'https://valuad-server-test.appspot.com/reportWin'; export const _VALUAD = (function() { const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); @@ -171,7 +172,8 @@ const converter = ortbConverter({ const site = getSite(bidderRequest); const session = getSession(); - const gdprConsent = getGdprConsent(bidderRequest) || {}; + const gdprConsent = getGdprConsent(bidderRequest).consentRequired || 0; + const gdprConsentString = getGdprConsent(bidderRequest).consentString || ""; const uspConsent = getUspConsent(bidderRequest) || {}; const coppa = getCoppa(); const { gpp, gpp_sid: gppSid } = deepAccess(bidderRequest, 'ortb2.regs', {}); @@ -184,10 +186,13 @@ const converter = ortbConverter({ deepSetValue(request, 'regs', { gdpr: gdprConsent, coppa: coppa, - ccpa: uspConsent, - gpp: gpp || '', - gppSid: gppSid || [], - dsa: dsa + us_privacy: uspConsent, + ext: { + gdpr_conset: gdprConsentString, + gpp: gpp || '', + gppSid: gppSid || [], + dsa: dsa, + } }); deepSetValue(request, 'site.ext.data.valuad_rtd', { @@ -336,6 +341,12 @@ const getUserSyncs = () => (syncOptions, serverResponses) => { return syncs; }; +const bidWonEvent = () => (bid) => { + const bidStr = JSON.stringify(bid); + const encodedBidStr = window.btoa(bidStr); + triggerPixel(WON_URL + '?b=' + encodedBidStr); +} + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], @@ -343,7 +354,8 @@ export const spec = { isBidRequestValid: isBidRequestValid(), buildRequests: buildRequests(AD_URL), interpretResponse: interpretResponse(), - getUserSyncs: getUserSyncs() + getUserSyncs: getUserSyncs(), + onBidWon: bidWonEvent() }; registerBidder(spec); From e14a0bac56c7a1801873732ff7dfe5ddb11bb494 Mon Sep 17 00:00:00 2001 From: natanavra Date: Mon, 24 Mar 2025 13:21:29 +0200 Subject: [PATCH 09/44] Remove ad creative/html from the bid won event --- modules/valuadBidAdapter.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index 0b32e418329..0b6f62b87e9 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -341,8 +341,9 @@ const getUserSyncs = () => (syncOptions, serverResponses) => { return syncs; }; -const bidWonEvent = () => (bid) => { - const bidStr = JSON.stringify(bid); +const onBidWon = (bid) => { + const { ad, ...restBid } = bid; + const bidStr = JSON.stringify(restBid); const encodedBidStr = window.btoa(bidStr); triggerPixel(WON_URL + '?b=' + encodedBidStr); } @@ -355,7 +356,7 @@ export const spec = { buildRequests: buildRequests(AD_URL), interpretResponse: interpretResponse(), getUserSyncs: getUserSyncs(), - onBidWon: bidWonEvent() + onBidWon, }; registerBidder(spec); From 274639f4d8568ac0e697a1e8116cf72205c28453 Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Mon, 24 Mar 2025 14:58:31 +0200 Subject: [PATCH 10/44] Fixed Coppa and uspConsent --- modules/valuadBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index 0b6f62b87e9..d201670e288 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -174,8 +174,8 @@ const converter = ortbConverter({ const gdprConsent = getGdprConsent(bidderRequest).consentRequired || 0; const gdprConsentString = getGdprConsent(bidderRequest).consentString || ""; - const uspConsent = getUspConsent(bidderRequest) || {}; - const coppa = getCoppa(); + const uspConsent = getUspConsent(bidderRequest).uspConsent || ""; + const coppa = getCoppa().required; const { gpp, gpp_sid: gppSid } = deepAccess(bidderRequest, 'ortb2.regs', {}); const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); From 37369f39a58b8070b848292bb1f4ef4a5cc78259 Mon Sep 17 00:00:00 2001 From: natanavra Date: Mon, 24 Mar 2025 15:02:15 +0200 Subject: [PATCH 11/44] Replace smartadserver URL transform with a change in the adapter --- modules/smartadserverBidAdapter.js | 2 +- package.json | 2 +- transform.js | 17 ----------------- 3 files changed, 2 insertions(+), 19 deletions(-) delete mode 100644 transform.js diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index a6ef9d4d0b3..90cc38a5cb4 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -164,7 +164,7 @@ export const spec = { createServerRequest: function(payload, domain) { return { method: 'POST', - url: (domain !== undefined ? domain : 'https://prg.smartadserver.com') + '/prebid/v1', + url: 'https://prg.smartadserver.com/prebid/v1', data: JSON.stringify(payload), options: { browsingTopics: false diff --git a/package.json b/package.json index bb4c4ba3624..213e3266d3d 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "./modules/*.js": "./modules/*.js" }, "scripts": { - "start": "gulp build --modules=modules.json && cp ./build/dist/prebid.js ../prebid.js && node transform.js", + "start": "gulp build --modules=modules.json && cp ./build/dist/prebid.js ../prebid.js", "serve": "gulp serve", "test": "gulp test", "lint": "gulp lint" diff --git a/transform.js b/transform.js deleted file mode 100644 index e98e899861a..00000000000 --- a/transform.js +++ /dev/null @@ -1,17 +0,0 @@ -const path = require('path'); -const fs = require('fs').promises; - -(async () => { - const filePath = path.join(__dirname, 'prebid.js'); - let pb = await fs.readFile(filePath, 'utf8'); - const regex = /url:\(void 0!==t\?t:"https:\/\/prg\.smartadserver\.com"\)\+"\/prebid\/v1"/ - if(regex.test(pb)) { - console.log('replacing smartadserver domain to hardcoded'); - pb = pb.replace(regex, 'url:"https://prg.smartadserver.com/prebid/v1"'); - } else { - await fs.unlink(filePath); - throw new Error('smartadserver domain not replaced') - } - await fs.writeFile(filePath, pb, 'utf8'); - console.log('done'); -})(); From c9ff3f635bfec04241b185407527c2df87b739d7 Mon Sep 17 00:00:00 2001 From: natanavra Date: Tue, 25 Mar 2025 14:15:47 +0200 Subject: [PATCH 12/44] Fix style --- modules/valuadBidAdapter.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index d201670e288..c585ee433ca 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -11,11 +11,11 @@ import { canAccessWindowTop, getDNT, logInfo, + triggerPixel, } from '../src/utils.js'; import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; import { config } from '../src/config.js'; import { parseDomain } from '../src/refererDetection.js'; -import { triggerPixel } from '../src/utils.js'; const BIDDER_CODE = 'valuad'; const AD_URL = 'https://valuad-server-test.appspot.com/adapter'; @@ -173,8 +173,8 @@ const converter = ortbConverter({ const session = getSession(); const gdprConsent = getGdprConsent(bidderRequest).consentRequired || 0; - const gdprConsentString = getGdprConsent(bidderRequest).consentString || ""; - const uspConsent = getUspConsent(bidderRequest).uspConsent || ""; + const gdprConsentString = getGdprConsent(bidderRequest).consentString || ''; + const uspConsent = getUspConsent(bidderRequest).uspConsent || ''; const coppa = getCoppa().required; const { gpp, gpp_sid: gppSid } = deepAccess(bidderRequest, 'ortb2.regs', {}); const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); From 5ed6cdeddd5cb43e8d812a576c16311de8cda033 Mon Sep 17 00:00:00 2001 From: natanavra Date: Tue, 25 Mar 2025 14:16:21 +0200 Subject: [PATCH 13/44] Fix setting _VALUAD.serverData wrong response field --- modules/valuadBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index c585ee433ca..3bb156f4e6d 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -320,9 +320,9 @@ const interpretResponse = () => (response, request) => { const bidResponses = converter.fromORTB({response: response.body, request: request.data}).bids; // Process server-side data - if (response.body && response.body.ext && response.body.ext.valuad_data) { + if (response.body && response.body.ext && response.body.ext.valuad) { // Store any server-side enhanced data for future use - _VALUAD.serverData = response.body.ext.valuad_data; + _VALUAD.serverData = response.body.ext.valuad; } return bidResponses; From 773f98b6f9706a7b69fb28dcebfa59bddaeb56d9 Mon Sep 17 00:00:00 2001 From: natanavra Date: Tue, 25 Mar 2025 14:16:52 +0200 Subject: [PATCH 14/44] Update bid won to send data to valuad analytics server added to bid response - vbid (demand) - vid (publisher) --- modules/valuadBidAdapter.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index 3bb156f4e6d..0c044b0d63e 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -19,7 +19,7 @@ import { parseDomain } from '../src/refererDetection.js'; const BIDDER_CODE = 'valuad'; const AD_URL = 'https://valuad-server-test.appspot.com/adapter'; -const WON_URL = 'https://valuad-server-test.appspot.com/reportWin'; +const WON_URL = 'https://hb-dot-valuad.appspot.com/adapter/win'; export const _VALUAD = (function() { const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); @@ -272,7 +272,18 @@ const converter = ortbConverter({ } return imp; - } + }, + + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + if (bid.vbid) { + bidResponse.vbid = bid.vbid; + } + if (context.bidRequest?.params?.placementId) { + bidResponse.vid = context.bidRequest.params.placementId; + } + return bidResponse; + }, }); const isBidRequestValid = () => (bid = {}) => { @@ -342,8 +353,12 @@ const getUserSyncs = () => (syncOptions, serverResponses) => { }; const onBidWon = (bid) => { - const { ad, ...restBid } = bid; - const bidStr = JSON.stringify(restBid); + const { + adUnitCode, adUnitId, auctionId, bidder, cpm, currency, originalCpm, originalCurrency, size, vbid, vid, + } = bid; + const bidStr = JSON.stringify({ + adUnitCode, adUnitId, auctionId, bidder, cpm, currency, originalCpm, originalCurrency, size, vbid, vid, + }); const encodedBidStr = window.btoa(bidStr); triggerPixel(WON_URL + '?b=' + encodedBidStr); } From 10473be284e31eda19e7431e24692255662279fc Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:31:55 +0200 Subject: [PATCH 15/44] Added support for schain --- adagio.js | 2725 +++++++++++++++++++++++++++++++++++ modules/valuadBidAdapter.js | 11 + 2 files changed, 2736 insertions(+) create mode 100644 adagio.js diff --git a/adagio.js b/adagio.js new file mode 100644 index 00000000000..d476504b040 --- /dev/null +++ b/adagio.js @@ -0,0 +1,2725 @@ +// hash: RgBOLRLarWLbdp3Ctr/cUVNfVGRqmeEgIYmJo2LMFDnFGlWZbuYVRV09tYM3d0rDIBJw9WRpgaJegnBSkPokzW6ThK3m3EClwCI71Z+cIZqYZk55Ja4oLOfjCSqpfSqj6CucvmqSNiJIwq5p4eLUGbwrdMFIpAiYvtpVpwHvykw= +var _ADAGIO = function(e) { + "use strict"; + function t(e, t) { + var n = Object.keys(e); + if (Object.getOwnPropertySymbols) { + var i = Object.getOwnPropertySymbols(e); + t && (i = i.filter((function(t) { + return Object.getOwnPropertyDescriptor(e, t).enumerable + } + ))), + n.push.apply(n, i) + } + return n + } + function n(e) { + for (var n = 1; n < arguments.length; n++) { + var i = null != arguments[n] ? arguments[n] : {}; + n % 2 ? t(Object(i), !0).forEach((function(t) { + d(e, t, i[t]) + } + )) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(i)) : t(Object(i)).forEach((function(t) { + Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(i, t)) + } + )) + } + return e + } + function i(e) { + var t = function(e, t) { + if ("object" != typeof e || !e) + return e; + var n = e[Symbol.toPrimitive]; + if (void 0 !== n) { + var i = n.call(e, t || "default"); + if ("object" != typeof i) + return i; + throw new TypeError("@@toPrimitive must return a primitive value.") + } + return ("string" === t ? String : Number)(e) + }(e, "string"); + return "symbol" == typeof t ? t : t + "" + } + function a(e) { + return (a = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(e) { + return typeof e + } + : function(e) { + return e && "function" == typeof Symbol && e.constructor === Symbol && e !== Symbol.prototype ? "symbol" : typeof e + } + )(e) + } + function r(e, t) { + if (!(e instanceof t)) + throw new TypeError("Cannot call a class as a function") + } + function s(e, t) { + for (var n = 0; n < t.length; n++) { + var a = t[n]; + a.enumerable = a.enumerable || !1, + a.configurable = !0, + "value"in a && (a.writable = !0), + Object.defineProperty(e, i(a.key), a) + } + } + function o(e, t, n) { + return t && s(e.prototype, t), + n && s(e, n), + Object.defineProperty(e, "prototype", { + writable: !1 + }), + e + } + function d(e, t, n) { + return (t = i(t))in e ? Object.defineProperty(e, t, { + value: n, + enumerable: !0, + configurable: !0, + writable: !0 + }) : e[t] = n, + e + } + function u(e) { + return function(e) { + if (Array.isArray(e)) + return l(e) + }(e) || function(e) { + if ("undefined" != typeof Symbol && null != e[Symbol.iterator] || null != e["@@iterator"]) + return Array.from(e) + }(e) || c(e) || function() { + throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.") + }() + } + function c(e, t) { + if (e) { + if ("string" == typeof e) + return l(e, t); + var n = Object.prototype.toString.call(e).slice(8, -1); + return "Object" === n && e.constructor && (n = e.constructor.name), + "Map" === n || "Set" === n ? Array.from(e) : "Arguments" === n || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n) ? l(e, t) : void 0 + } + } + function l(e, t) { + (null == t || t > e.length) && (t = e.length); + for (var n = 0, i = new Array(t); n < t; n++) + i[n] = e[n]; + return i + } + var f = ["120x600", "160x600", "300x250", "300x600", "300x1050", "600x250", "600x600", "728x90", "728x94", "728x315", "800x600", "970x90", "970x250", "1000x200"] + , v = 0 + , h = 1 + , p = Array.isArray + , g = "object" == typeof global && global && global.Object === Object && global + , m = "object" == typeof self && self && self.Object === Object && self + , b = g || m || Function("return this")() + , y = b.Symbol + , A = Object.prototype + , w = A.hasOwnProperty + , I = A.toString + , _ = y ? y.toStringTag : void 0; + var O = Object.prototype.toString; + var D = y ? y.toStringTag : void 0; + function E(e) { + return null == e ? void 0 === e ? "[object Undefined]" : "[object Null]" : D && D in Object(e) ? function(e) { + var t = w.call(e, _) + , n = e[_]; + try { + e[_] = void 0; + var i = !0 + } catch (e) {} + var a = I.call(e); + return i && (t ? e[_] = n : delete e[_]), + a + }(e) : function(e) { + return O.call(e) + }(e) + } + function S(e) { + return null != e && "object" == typeof e + } + function U(e) { + return "symbol" == typeof e || S(e) && "[object Symbol]" == E(e) + } + var C = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/ + , x = /^\w*$/; + function G(e) { + var t = typeof e; + return null != e && ("object" == t || "function" == t) + } + var j, k = b["__core-js_shared__"], N = (j = /[^.]+$/.exec(k && k.keys && k.keys.IE_PROTO || "")) ? "Symbol(src)_1." + j : ""; + var P = Function.prototype.toString; + var R = /^\[object .+?Constructor\]$/ + , T = Function.prototype + , B = Object.prototype + , L = T.toString + , z = B.hasOwnProperty + , M = RegExp("^" + L.call(z).replace(/[\\^$.*+?()[\]{}|]/g, "\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, "$1.*?") + "$"); + function V(e) { + return !(!G(e) || (t = e, + N && N in t)) && (function(e) { + if (!G(e)) + return !1; + var t = E(e); + return "[object Function]" == t || "[object GeneratorFunction]" == t || "[object AsyncFunction]" == t || "[object Proxy]" == t + }(e) ? M : R).test(function(e) { + if (null != e) { + try { + return P.call(e) + } catch (e) {} + try { + return e + "" + } catch (e) {} + } + return "" + }(e)); + var t + } + function F(e, t) { + var n = function(e, t) { + return null == e ? void 0 : e[t] + }(e, t); + return V(n) ? n : void 0 + } + var q = F(Object, "create"); + var H = Object.prototype.hasOwnProperty; + var $ = Object.prototype.hasOwnProperty; + function W(e) { + var t = -1 + , n = null == e ? 0 : e.length; + for (this.clear(); ++t < n; ) { + var i = e[t]; + this.set(i[0], i[1]) + } + } + function J(e, t) { + return e === t || e != e && t != t + } + function Q(e, t) { + for (var n = e.length; n--; ) + if (J(e[n][0], t)) + return n; + return -1 + } + W.prototype.clear = function() { + this.__data__ = q ? q(null) : {}, + this.size = 0 + } + , + W.prototype.delete = function(e) { + var t = this.has(e) && delete this.__data__[e]; + return this.size -= t ? 1 : 0, + t + } + , + W.prototype.get = function(e) { + var t = this.__data__; + if (q) { + var n = t[e]; + return "__lodash_hash_undefined__" === n ? void 0 : n + } + return H.call(t, e) ? t[e] : void 0 + } + , + W.prototype.has = function(e) { + var t = this.__data__; + return q ? void 0 !== t[e] : $.call(t, e) + } + , + W.prototype.set = function(e, t) { + var n = this.__data__; + return this.size += this.has(e) ? 0 : 1, + n[e] = q && void 0 === t ? "__lodash_hash_undefined__" : t, + this + } + ; + var K = Array.prototype.splice; + function Z(e) { + var t = -1 + , n = null == e ? 0 : e.length; + for (this.clear(); ++t < n; ) { + var i = e[t]; + this.set(i[0], i[1]) + } + } + Z.prototype.clear = function() { + this.__data__ = [], + this.size = 0 + } + , + Z.prototype.delete = function(e) { + var t = this.__data__ + , n = Q(t, e); + return !(n < 0) && (n == t.length - 1 ? t.pop() : K.call(t, n, 1), + --this.size, + !0) + } + , + Z.prototype.get = function(e) { + var t = this.__data__ + , n = Q(t, e); + return n < 0 ? void 0 : t[n][1] + } + , + Z.prototype.has = function(e) { + return Q(this.__data__, e) > -1 + } + , + Z.prototype.set = function(e, t) { + var n = this.__data__ + , i = Q(n, e); + return i < 0 ? (++this.size, + n.push([e, t])) : n[i][1] = t, + this + } + ; + var X = F(b, "Map"); + function Y(e, t) { + var n, i, a = e.__data__; + return ("string" == (i = typeof (n = t)) || "number" == i || "symbol" == i || "boolean" == i ? "__proto__" !== n : null === n) ? a["string" == typeof t ? "string" : "hash"] : a.map + } + function ee(e) { + var t = -1 + , n = null == e ? 0 : e.length; + for (this.clear(); ++t < n; ) { + var i = e[t]; + this.set(i[0], i[1]) + } + } + ee.prototype.clear = function() { + this.size = 0, + this.__data__ = { + hash: new W, + map: new (X || Z), + string: new W + } + } + , + ee.prototype.delete = function(e) { + var t = Y(this, e).delete(e); + return this.size -= t ? 1 : 0, + t + } + , + ee.prototype.get = function(e) { + return Y(this, e).get(e) + } + , + ee.prototype.has = function(e) { + return Y(this, e).has(e) + } + , + ee.prototype.set = function(e, t) { + var n = Y(this, e) + , i = n.size; + return n.set(e, t), + this.size += n.size == i ? 0 : 1, + this + } + ; + function te(e, t) { + if ("function" != typeof e || null != t && "function" != typeof t) + throw new TypeError("Expected a function"); + var n = function() { + var i = arguments + , a = t ? t.apply(this, i) : i[0] + , r = n.cache; + if (r.has(a)) + return r.get(a); + var s = e.apply(this, i); + return n.cache = r.set(a, s) || r, + s + }; + return n.cache = new (te.Cache || ee), + n + } + te.Cache = ee; + var ne = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g + , ie = /\\(\\)?/g + , ae = function(e) { + var t = te(e, (function(e) { + return 500 === n.size && n.clear(), + e + } + )) + , n = t.cache; + return t + }((function(e) { + var t = []; + return 46 === e.charCodeAt(0) && t.push(""), + e.replace(ne, (function(e, n, i, a) { + t.push(i ? a.replace(ie, "$1") : n || e) + } + )), + t + } + )); + var re = y ? y.prototype : void 0 + , se = re ? re.toString : void 0; + function oe(e) { + if ("string" == typeof e) + return e; + if (p(e)) + return function(e, t) { + for (var n = -1, i = null == e ? 0 : e.length, a = Array(i); ++n < i; ) + a[n] = t(e[n], n, e); + return a + }(e, oe) + ""; + if (U(e)) + return se ? se.call(e) : ""; + var t = e + ""; + return "0" == t && 1 / e == -1 / 0 ? "-0" : t + } + function de(e, t) { + return p(e) ? e : function(e, t) { + if (p(e)) + return !1; + var n = typeof e; + return !("number" != n && "symbol" != n && "boolean" != n && null != e && !U(e)) || (x.test(e) || !C.test(e) || null != t && e in Object(t)) + }(e, t) ? [e] : ae(function(e) { + return null == e ? "" : oe(e) + }(e)) + } + function ue(e) { + if ("string" == typeof e || U(e)) + return e; + var t = e + ""; + return "0" == t && 1 / e == -1 / 0 ? "-0" : t + } + function ce(e, t) { + for (var n = 0, i = (t = de(t, e)).length; null != e && n < i; ) + e = e[ue(t[n++])]; + return n && n == i ? e : void 0 + } + var le = Object.prototype.hasOwnProperty; + function fe(e, t) { + return null != e && le.call(e, t) + } + function ve(e) { + return S(e) && "[object Arguments]" == E(e) + } + var he = Object.prototype + , pe = he.hasOwnProperty + , ge = he.propertyIsEnumerable + , me = ve(function() { + return arguments + }()) ? ve : function(e) { + return S(e) && pe.call(e, "callee") && !ge.call(e, "callee") + } + , be = /^(?:0|[1-9]\d*)$/; + function ye(e, t) { + var n = typeof e; + return !!(t = null == t ? 9007199254740991 : t) && ("number" == n || "symbol" != n && be.test(e)) && e > -1 && e % 1 == 0 && e < t + } + function Ae(e, t) { + return null != e && function(e, t, n) { + for (var i, a = -1, r = (t = de(t, e)).length, s = !1; ++a < r; ) { + var o = ue(t[a]); + if (!(s = null != e && n(e, o))) + break; + e = e[o] + } + return s || ++a != r ? s : !!(r = null == e ? 0 : e.length) && ("number" == typeof (i = r) && i > -1 && i % 1 == 0 && i <= 9007199254740991) && ye(o, r) && (p(e) || me(e)) + }(e, t, fe) + } + var we = function() { + try { + var e = F(Object, "defineProperty"); + return e({}, "", {}), + e + } catch (e) {} + }(); + var Ie = Object.prototype.hasOwnProperty; + function _e(e, t, n) { + var i = e[t]; + Ie.call(e, t) && J(i, n) && (void 0 !== n || t in e) || function(e, t, n) { + "__proto__" == t && we ? we(e, t, { + configurable: !0, + enumerable: !0, + value: n, + writable: !0 + }) : e[t] = n + }(e, t, n) + } + function Oe(e, t, n) { + return null == e ? e : function(e, t, n, i) { + if (!G(e)) + return e; + for (var a = -1, r = (t = de(t, e)).length, s = r - 1, o = e; null != o && ++a < r; ) { + var d = ue(t[a]) + , u = n; + if ("__proto__" === d || "constructor" === d || "prototype" === d) + return e; + if (a != s) { + var c = o[d]; + void 0 === (u = i ? i(c, d, o) : void 0) && (u = G(c) ? c : ye(t[a + 1]) ? [] : {}) + } + _e(o, d, u), + o = o[d] + } + return e + }(e, t, n) + } + function De(e, t) { + return t.length < 2 ? e : ce(e, function(e, t, n) { + var i = -1 + , a = e.length; + t < 0 && (t = -t > a ? 0 : a + t), + (n = n > a ? a : n) < 0 && (n += a), + a = t > n ? 0 : n - t >>> 0, + t >>>= 0; + for (var r = Array(a); ++i < a; ) + r[i] = e[i + t]; + return r + }(t, 0, -1)) + } + function Ee(e, t) { + return null == e || function(e, t) { + return null == (e = De(e, t = de(t, e))) || delete e[ue((n = t, + i = null == n ? 0 : n.length, + i ? n[i - 1] : void 0))]; + var n, i + }(e, t) + } + var Se = function() { + return o((function e() { + r(this, e), + this.init() + } + ), [{ + key: "init", + value: function() { + this.w = window, + this.create() + } + }, { + key: "create", + value: function() { + this.w.localStorage.getItem("adagio") || this.w.localStorage.setItem("adagio", JSON.stringify({})) + } + }, { + key: "insureSchema", + value: function() { + var e = this.w.localStorage.getItem("adagio"); + try { + !(!e || !JSON.parse(e)) || (this.w.localStorage.removeItem("adagio"), + this.create()) + } catch (e) { + this.w.localStorage.removeItem("adagio"), + this.create() + } + } + }, { + key: "get", + value: function(e) { + this.insureSchema(); + var t = JSON.parse(this.w.localStorage.getItem("adagio")); + return e ? Ae(t, e) ? function(e, t, n) { + var i = null == e ? void 0 : ce(e, t); + return void 0 === i ? n : i + }(t, e) : null : t + } + }, { + key: "store", + value: function(e, t) { + this.insureSchema(); + var n = JSON.parse(this.w.localStorage.getItem("adagio")); + Oe(n, e, t), + this.w.localStorage.setItem("adagio", JSON.stringify(n)) + } + }, { + key: "unset", + value: function(e) { + var t = JSON.parse(this.w.localStorage.getItem("adagio")); + Ee(t, e), + this.w.localStorage.setItem("adagio", JSON.stringify(t)) + } + }]) + }() + , Ue = function() { + try { + if (window.top.location.href) + return !0 + } catch (e) { + return !1 + } + } + , Ce = function() { + return Ue() ? window.top : window.self + } + , xe = { + default: "\n background: #222;\n color: #bada55;\n border-radius: 4px 0 0 4px;\n padding: 3px 4px 2px;\n font-weight: normal;\n ", + reset: "\n background: transparent;\n color: inherit;\n border-radius: 0;\n padding: 0;\n font-weight: normal;\n ", + debug: "\n background: palegreen;\n color: darkgreen;\n border-radius: 0 4px 4px 0;\n padding: 3px 4px 2px;\n margin-right: 10px;\n font-weight: normal;\n ", + warn: "\n background: lightcoral;\n color: moccasin;\n border-radius: 0 4px 4px 0;\n padding: 3px 4px 2px;\n margin-right: 10px;\n font-weight: normal;\n ", + error: "\n background: firebrick;\n color: gainsboro;\n border-radius: 0 4px 4px 0;\n padding: 3px 4px 2px;\n margin-right: 10px;\n font-weight: normal;\n " + } + , Ge = function(e) { + e = e || "debug"; + for (var t = arguments.length, n = new Array(t > 1 ? t - 1 : 0), i = 1; i < t; i++) + n[i - 1] = arguments[i]; + var a; + "string" == typeof n[0] ? n[1] ? (console.log("%cADG%c".concat(e.toUpperCase(), " %c%s"), xe.default, xe[e], xe.reset, n[0]), + n.shift(), + n.map((function(e) { + console.log(e) + } + ))) : console.log("%cADG%c".concat(e.toUpperCase(), " %c%s"), xe.default, xe[e], xe.reset, n[0]) : (a = console).log.apply(a, ["%cADG%c".concat(e.toUpperCase()), xe.default, xe[e]].concat(n)) + } + , je = function() { + var e = Ce(); + return e && e.localStorage && e.localStorage.getItem("ADAGIO_DEV_DEBUG") + } + , ke = function() { + if (je()) { + for (var e = arguments.length, t = new Array(e), n = 0; n < e; n++) + t[n] = arguments[n]; + Ge.apply(void 0, ["warn"].concat(t)) + } + } + , Ne = function() { + if (je()) { + for (var e = arguments.length, t = new Array(e), n = 0; n < e; n++) + t[n] = arguments[n]; + Ge.apply(void 0, ["error"].concat(t)) + } + } + , Pe = function() { + if (je()) { + for (var e = arguments.length, t = new Array(e), n = 0; n < e; n++) + t[n] = arguments[n]; + Ge.apply(void 0, ["debug"].concat(t)) + } + } + , Re = function e(t) { + return t ? (t ^ 16 * Math.random() >> t / 4).toString(16) : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, e) + } + , Te = function() { + var e, t; + switch (e = Ce().navigator.userAgent, + /(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(e) ? 5 : /Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile|Kindle|NetFront|Silk-Accelerated|(hpw|web)OS|Fennec|Minimo|Opera M(obi|ini)|Blazer|Dolfin|Dolphin|Skyfire|Zune/.test(e) ? 4 : 2) { + case 2: + t = "desktop"; + break; + case 4: + t = "mobile"; + break; + case 5: + t = "tablet" + } + return t + } + , Be = function(e) { + if ("string" != typeof e || "http" !== e.slice(0, 4)) + return Pe("uriParser: unable to parse uri, invalid", e), + !1; + var t = document.createElement("a") + , n = "" + , i = {}; + t.href = e; + for (var a = 0, r = (n = t.search.slice(1).split("&")).length; a < r; a++) { + var s = n[a].split("="); + i[s[0]] = s[1] + } + return { + protocol: t.protocol, + hostname: t.hostname, + port: t.port, + pathname: t.pathname, + search: t.search, + searchParsed: i, + hash: t.hash, + host: t.host + } + } + , Le = function() { + var e = Ce() + , t = null; + return e.performance && e.performance.timing && e.performance.timing.domContentLoadedEventStart && (t = e.performance.timing.domContentLoadedEventStart), + t + } + , ze = function() { + return o((function e() { + r(this, e), + this.storage = new Se, + this.maxTimeSession = 18e5, + this._ensureSchema() + } + ), [{ + key: "_ensureSchema", + value: function() { + var e = this.storage.get("navigation") + , t = this.storage.get("navigation.session"); + if (t) { + if (t.sampling) + try { + var n = t.sampling.avw + , i = t.sampling.rates.avw; + t.rnd = !0 === n ? i + .01 : i - .01, + t.vwSmplg = i, + t.vwSmplgNxt = e && e.nextSamplingRates && e.nextSamplingRates.avw ? e.nextSamplingRates.avw : i, + t._firstPageviewId = t.sampling.firstPageviewId, + t._bidsSmplg = .1, + t._currentPagetype = t.currentPagetype, + t._previousPagetype = t.previousPagetype, + delete t.sampling, + delete t.currentPagetype, + delete t.previousPagetype + } catch (e) {} + this.storage.store("session", t), + this.storage.unset("navigation.session"), + delete e.session, + delete e.nextSamplingRates + } + e && (this.storage.store("_navigation", e), + this.storage.unset("navigation")) + } + }, { + key: "startOrUpdate", + value: function(e) { + var t = this + , n = Date.now() + , i = this.storage.get("_navigation") + , a = this.storage.get("session") + , r = e && e.v && e.v >= 2; + try { + !i || !a || (r ? e.isNew : e.isNew && e.initiator) || "number" != typeof a.vwSmplg || (r ? "number" == typeof a.expiry && n > a.expiry : "number" == typeof a.lastActivityTime && n - a.lastActivityTime > t.maxTimeSession) ? this.start(e) : this.update(e) + } catch (e) { + Pe(e) + } + } + }, { + key: "start", + value: function(e) { + var t = this.storage.get("_navigation") || {} + , n = parseInt(t.totalPages, 10) || 0 + , i = parseInt(t.totalSessions, 10) || 0 + , a = this.storage.get("session") || {}; + this.storage.store("_navigation.totalPages", n + 1), + this.storage.store("_navigation.totalSessions", i + 1); + var r, s, o, d, u = Date.now(), c = a.vwSmplgNxt || .1, l = e && e.v && e.v >= 2; + l ? (r = e && e.rnd ? e.rnd : Math.random(), + s = e && e.id ? e.id : Re()) : Object.keys(a).length && "snippet" === a.initiator ? (s = a.id, + r = a.rnd, + o = a.testName, + d = a.testVersion) : (s = e && e.id ? e.id : Re(), + r = e && e.rnd ? e.rnd : Math.random()); + var f = { + _firstPageviewId: null, + _currentPagetype: null, + _previousPagetype: null, + _bidsSmplg: .1, + pages: 1, + rnd: r, + id: s, + new: !0, + vwSmplg: c, + vwSmplgNxt: c + }; + f.expiry = u + this.maxTimeSession, + l ? f._v = 2 : (f.lastActivityTime = u, + f.initiator = "adgjs", + o && (f.testName = o), + d && (f.testVersion = d)), + this.storage.store("session", f) + } + }, { + key: "update", + value: function(e) { + var t = Date.now() + , n = this.storage.get("_navigation") + , i = this.storage.get("session"); + if (!n || !i) + throw new Error("Key missing in localStorage"); + var a = i._v && i._v >= 2 || e && e.v && e.v >= 2; + a && (this.storage.unset("session.testName"), + this.storage.unset("session.testVersion"), + this.storage.unset("session.lastActivityTime"), + this.storage.store("session._v", 2)), + i._pages && (i.pages = i._pages, + this.storage.unset("session._pages")); + var r = parseInt(i.pages, 10) || 0 + , s = parseInt(n.totalPages, 10) || 0; + this.storage.store("session.expiry", t + this.maxTimeSession), + a || (this.storage.store("session.lastActivityTime", t), + this.storage.store("session.initiator", "adgjs")), + this.storage.store("session.new", !1), + this.storage.store("session.pages", r + 1), + this.storage.store("_navigation.totalPages", s + 1) + } + }, { + key: "setVwSamplingNext", + value: function(e) { + this.storage.store("session.vwSmplgNxt", e) + } + }, { + key: "setSampling", + value: function(e, t) { + var n = this.storage.get("session._firstPageviewId"); + if (!this.storage.get("session.rnd")) + throw new Error("The key rnd has not been found"); + n ? n === e && t && "number" == typeof t.vwSmplgNxt && t.vwSmplgNxt >= 0 && (this.storage.store("session.vwSmplg", t.vwSmplgNxt), + this.storage.store("session.vwSmplgNxt", t.vwSmplgNxt)) : this.storage.store("session._firstPageviewId", e) + } + }]) + }() + , Me = function() { + return o((function e() { + r(this, e), + this._storage = new Se + } + ), [{ + key: "sessionLength", + get: function() { + return this._storage.get("session.pages") || 1 + } + }, { + key: "avgSessionLength", + get: function() { + var e = parseInt(this._storage.get("_navigation.totalSessions"), 10) || 1; + return (parseInt(this._storage.get("_navigation.totalPages"), 10) || 1) / e + } + }, { + key: "referrerFQDN", + get: function() { + var e = ""; + if (Ue()) { + var t = window.top.document.referrer; + if (t) + e = Be(t).hostname + } + return e + } + }, { + key: "totalSessions", + get: function() { + return this._storage.get("_navigation.totalSessions") || 1 + } + }, { + key: "previousPagetype", + get: function() { + return this._storage.get("session._previousPagetype") + } + }, { + key: "currentPagetype", + get: function() { + return this._storage.get("session._currentPagetype") + } + }, { + key: "allowBeaconSending", + value: function(e) { + var t, n = arguments.length > 1 && void 0 !== arguments[1] && arguments[1]; + if (-1 === ["avw", "bids"].indexOf(e)) + return !0; + switch (e) { + case "bids": + t = "number" == typeof n ? n : this._storage.get("session._bidsSmplg"), + 0 === this._storage.get("session.vwSmplg") && (t = 0); + break; + case "avw": + t = this._storage.get("session.vwSmplg") + } + var i = this._storage.get("session.rnd"); + return "number" != typeof t || "number" != typeof i || i < t + } + }, { + key: "pageType", + set: function(e) { + var t = this.currentPagetype; + null != t && this._storage.store("session._previousPagetype", t), + this._storage.store("session._currentPagetype", e) + } + }]) + }() + , Ve = { + avw: "//c.4dex.tech/avw.gif" + } + , Fe = [] + , qe = !!navigator.sendBeacon + , He = Ce(); + He.ADAGIO = He.ADAGIO || {}; + var $e = function(e) { + var t, n = function(e) { + var t = e.collector + , n = e.data + , i = e.eventType + , a = []; + if (!Ve[t]) + return !1; + if (!(n = "function" == typeof n ? n() : {})) + return !1; + if (n.org_id && -1 !== Fe.indexOf(n.org_id)) + return Pe("Beacon cannot be sent due to blacklist"), + !1; + if (n.adu_code) { + var r = n.adu_code; + delete n.adu_code, + a.push(encodeURIComponent("adu_code") + "=" + encodeURIComponent(r)) + } + var s = i || n.evt || ""; + for (var o in delete n.evt, + a.push(encodeURIComponent("evt") + "=" + encodeURIComponent(s)), + n) + a.push(encodeURIComponent(o) + "=" + encodeURIComponent(n[o])); + return He.ADAGIO.versions && (He.ADAGIO.versions.adagiojs && a.push("adgjsv=".concat(encodeURIComponent(He.ADAGIO.versions.adagiojs))), + He.ADAGIO.versions.ssp && a.push("sspv=".concat(encodeURIComponent(He.ADAGIO.versions.ssp)))), + He.location.protocol + Ve[t] + "?" + a.join("&") + }({ + collector: e.collector, + data: e.data, + eventType: e.eventType + }); + if (!n) + return !1; + try { + (t = new XMLHttpRequest).onerror = function() { + qe && navigator.sendBeacon(n) + } + , + t.open("post", n), + t.send() + } catch (e) {} + } + , We = function(e) { + var t = e.event + , n = e.collector + , i = e.data + , a = t.element || He; + t._rule ? "unload" === t._rule ? He.addEventListener("unload", (function() { + return $e({ + collector: n, + data: i, + eventType: "unload" + }) + } + )) : "visibilitychange" === t._rule && He.document.addEventListener("visibilitychange", (function() { + "hidden" === He.document.visibilityState && $e({ + collector: n, + data: i, + eventType: "visibilitychange" + }) + } + )) : a.addEventListener(t.name, (function() { + if ("function" == typeof t.beforeSend) { + var e = t.beforeSend({ + collector: n, + data: i + }); + e && $e(Object.assign({}, e, { + eventType: t.name + })) + } else + $e({ + collector: n, + data: i, + eventType: t.name + }) + } + ), !1) + } + , Je = function(e) { + var t = e.collector + , n = e.data + , i = e.events; + if (i.length) { + var a, r = function(e, t) { + var n = "undefined" != typeof Symbol && e[Symbol.iterator] || e["@@iterator"]; + if (!n) { + if (Array.isArray(e) || (n = c(e)) || t && e && "number" == typeof e.length) { + n && (e = n); + var i = 0 + , a = function() {}; + return { + s: a, + n: function() { + return i >= e.length ? { + done: !0 + } : { + done: !1, + value: e[i++] + } + }, + e: function(e) { + throw e + }, + f: a + } + } + throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.") + } + var r, s = !0, o = !1; + return { + s: function() { + n = n.call(e) + }, + n: function() { + var e = n.next(); + return s = e.done, + e + }, + e: function(e) { + o = !0, + r = e + }, + f: function() { + try { + s || null == n.return || n.return() + } finally { + if (o) + throw r + } + } + } + }(i); + try { + for (r.s(); !(a = r.n()).done; ) { + var s = a.value; + We({ + event: s, + collector: t, + data: n + }) + } + } catch (e) { + r.e(e) + } finally { + r.f() + } + } else + $e({ + collector: t, + data: n + }) + } + , Qe = function e(t, n) { + if (!n || !n.length) + return !1; + if (!t || "function" != typeof t.getBoundingClientRect) + return !1; + try { + var i = t.getBoundingClientRect() + , a = Math.round(i.width) + "x" + Math.round(i.height); + if (-1 !== n.indexOf(a)) + return t; + var r = t.querySelectorAll("*:not(script)"); + if (r.length) { + for (var s = 0, o = r.length; s < o; s++) + if (t = e(r[s], n)) + return t; + return !1 + } + } catch (e) {} + return !1 + } + , Ke = function(e) { + var t = new Date(e.detail.ts).toString() + , n = ""; + e.detail.data.event && e.detail.data.event.slot ? n = e.detail.data.event.slot.getSlotElementId() : e.detail.data.args && e.detail.data.args.slot && (n = e.detail.data.args.slot.getSlotElementId()), + n === this._adUnitElementId && (Pe("GPT impressionViewable: ".concat(n, " at ").concat(t)), + this.viewability.adserver.visible = !0, + this.viewability.adserver.viewableSince = e.detail.ts, + this.viewability.adserver.exposureDelta = this.viewability.adagio.exposureDuration - 1e3, + this.sendBeacon({ + becauseOf: "vsbl_actvw" + })) + } + , Ze = function(e) { + var t = e.detail.data.args + , n = t.inViewPercentage + , i = t.slot; + return !!i && (i.getSlotElementId() === this._adUnitElementId && void (this.viewability.adserver.inViewport = n >= 50)) + } + , Xe = function() { + "hidden" === this.w.document.visibilityState && (this.hasMaxExposureDuration() || this.sendBeacon({ + becauseOf: "visibilitychange" + })) + } + , Ye = function() { + var e = Date.now(); + "hidden" === this.w.document.visibilityState ? this.pageVisibility.ts = e : (this.pageVisibility.computedDuration += e - this.pageVisibility.ts, + this.pageVisibility.ts = !1) + } + , et = function(e, t, n) { + var i = e.adUnitCode + , a = e.adUnitElementId + , r = e.refreshConfig + , s = e.currentPrintNumber + , o = e.timeout + , d = Ce(); + if (d.googletag) { + var u = d.googletag.pubads().getSlots().filter((function(e) { + return e.getSlotElementId() === a + } + ))[0] + , c = function(e, t) { + var n = e.pbjs + , i = e.ADAGIO.pbjsAdUnits.find((function(e) { + return e.code = t + } + )); + return i && i.localPbjsRef && (n = i.localPbjsRef), + n + }(d, i); + u ? (u.setTargeting("adg_refresh", "true"), + s && u.setTargeting("adg_pn", parseInt(s.toString(), 10) + 1), + t({ + adUnitCode: i, + adUnitElementId: a, + refreshConfig: r, + currentPrintNumber: s + }).then((function(e) { + !1 !== e && n({ + pbjs: c, + adUnitCode: i, + timeout: o, + bidsBackHandler: function() { + c.setTargetingForGPTAsync(a), + d.googletag.pubads().refresh([u]) + } + }) + } + ))) : Pe("No slot detected for adunitCode " + i + " with elementId : " + a) + } else + ke("Can not find the property: googletag in window") + } + , tt = function(e, t) { + var n = e.adUnitCode + , i = e.adUnitElementId + , a = e.refreshConfig + , r = Ce(); + if (r.sas) { + var s = r.ADAGIO.adUnits; + !s || s[n] ? t({ + adUnitCode: n, + adUnitElementId: i, + refreshConfig: a + }).then((function(e) { + if (!1 !== e) + try { + r.sas.refresh(n) + } catch (e) { + Ne(e) + } + } + )) : Pe("No slot detected for adunitCode " + n + " with elementId : " + i) + } else + ke("Can not find the property: sas in window") + } + , nt = function(e) { + if (!e.refreshConfig || !e.refreshConfig.beforeRefresh || "function" != typeof e.refreshConfig.beforeRefresh) + return new Promise((function(e) { + return e(!0) + } + )); + var t = e.refreshConfig.beforeRefresh(e); + return Pe("Refresh: handleBeforeRefresh", t), + new Promise((function(e, n) { + if (!(t instanceof Promise)) + return e(t); + t.then((function(t) { + return e(t) + } + )).catch((function() { + return n(!1) + } + )) + } + )) + } + , it = function(e) { + var t = e.adUnitCode + , n = e.bidsBackHandler + , i = e.pbjs + , a = e.timeout; + i.que.push((function() { + i.requestBids({ + timeout: a, + adUnitCodes: [t], + bidsBackHandler: n + }) + } + )) + } + , at = ["1012"] + , rt = { + page_dimensions: "pg_dims", + viewport_dimensions: "vp_dims", + dom_loading: "dom_l", + layout: "lay", + adunit_position: "adu_pos", + user_timestamp: "u_ts", + device: "dvc", + browser: "brwsr", + url: "url", + print_number: "pn" + } + , st = { + organizationId: "org_id", + site: "site", + placement: "plcmt", + adUnitCode: "adu_code", + pagetype: "pgtyp", + category: "cat", + subcategory: "subcat", + environment: "env" + } + , ot = { + adsrv: "adsrv", + adsrv_advrt_id: "adsrv_advrt_id", + adsrv_cmpgn_id: "adsrv_cmpgn_id", + adsrv_crea_id: "adsrv_crea_id", + adsrv_empty: "adsrv_empty", + adsrv_lnitem_id: "adsrv_lnitem_id", + adsrv_size: "adsrv_size" + } + , dt = function() { + var e = Ce(); + if (e) + return e.ONFOCUS = e.ONFOCUS || {}, + e.ONFOCUS.donotrefresh || e.onfocus_donotrefresh || e.onfocus_donotrefresh_slots || e.ONFOCUS.donotrefresh_slots + } + , ut = function() { + return o((function e(t) { + var n = t.ts + , i = t.adUnitElementId + , a = t.auctionId + , s = t.params + , o = t.options + , d = t.featuresManager + , u = t.measurersManager + , c = t.navigationFeatures; + return r(this, e), + Pe("New measurer for adUnitElementId ".concat(i, ":"), { + params: s, + options: o + }), + this.w = Ce(), + this.navigationFeatures = c, + this.featuresManager = d, + this.measurersManager = u, + this.params = s || {}, + this.options = o || {}, + this.auctionId = a, + this.initTime = null, + this.startTime = null, + this.ts = n, + this.navigationStart = function() { + var e = Ce() + , t = e.performance || e.msPerformance || e.webkitPerformance || e.mozPerformance; + return t && t.timing && t.timing.navigationStart > 0 ? t.timing.navigationStart : null + }(), + this.internalId = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), + this.resetCounter = 0, + this.resetTime = null, + this.beaconVersion = 0, + this.intervalId = !1, + this.avwBeaconTimeoutId = !1, + this.refreshStarted = !1, + this.refreshConfig = this.options.refresh || !1, + this.doNotRefresh = !1, + this.useIntersectionObserver = !0, + this.clickListenerHandler = null, + this.mouseHoverListenerHandler = null, + this.mouseOutListenerHandler = null, + this.gptImpressionViewable = Ke.bind(this), + this.gptSlotVisibilityChanged = Ze.bind(this), + this.pageVisibility = { + ts: !1, + computedDuration: 0 + }, + this._adUnitElementId = this.adUnitElementId = i, + this.element = {}, + this.prebidAdUnitConfig = this.options.adUnitConfig || [], + this.throttleBeacons = "boolean" != typeof this.options.throttleBeacons || this.options.throttleBeacons, + this.beaconsQueue = [], + this.beaconsPending = !1, + this.limitFirstBeaconsTimer = !1, + window.self.document.getElementById(this.adUnitElementId) ? this.init() ? (this.unbindBeaconEvents(), + this.bindBeaconEvents(), + void this.start()) : (ke("Unable to init measurer"), + !1) : (ke("Element to measure is missing in window: ".concat(this.adUnitElementId)), + !1) + } + ), [{ + key: "init", + value: function() { + this.initTime = Date.now(), + this.measurable = !0; + var e = this.getFeatures(this._adUnitElementId); + if (this.features = e && e.features ? e.features : {}, + this.featuresVersion = e && e.version ? e.version : "1", + this.clickListenerHandler = this.clickListener.bind(this), + this.mouseHoverListenerHandler = this.mouseHoverListener.bind(this), + this.mouseOutListenerHandler = this.mouseOutListener.bind(this), + this.resetViewability(), + !window.IntersectionObserver) + return this.useIntersectionObserver = !1, + this.measurable = !1, + ke("no intersection observer"), + void this.stop("noIObserver"); + if ("function" != typeof window.CustomEvent) + return this.stop("noCustomEvent"), + !1; + this.bindAdserverEvents(); + var t = this.detectBestDomElement(); + if (!t) + return this.stop("noElement"), + !1; + this.setElement(t), + this.startBackgroundDetection(), + Pe("Init measurer for adUnitElementId ".concat(this.adUnitElementId, " with this detected element:"), { + el: this.element.el, + refresh: this.refreshConfig + }), + this.w.ADAGIO = this.w.ADAGIO || {}, + this.w.ADAGIO.doNotRefresh = dt() || this.w.ADAGIO.doNotRefresh || []; + var n = this.params.adUnitCode || !1; + return this.refreshConfig || (Pe("No refreshConfig for this Measurer: ".concat(n)), + this.doNotRefresh = !0), + n || (Pe("No adUnitCode for this Measurer: ".concat(n)), + this.doNotRefresh = !0), + Array.isArray(this.w.ADAGIO.doNotRefresh) && -1 !== this.w.ADAGIO.doNotRefresh.indexOf("*") && (Pe("No Refresh because doNotRefresh is activate on the whole page"), + this.doNotRefresh = !0), + Array.isArray(this.w.ADAGIO.doNotRefresh) && n && -1 !== this.w.ADAGIO.doNotRefresh.indexOf(n) && (Pe("This adUnitCode is in the ADAGIO.doNotRefresh: ".concat(n)), + this.doNotRefresh = !0), + !0 + } + }, { + key: "resetViewability", + value: function(e) { + var t = { + adagio: { + visible: !1, + continuousCounter: 0, + viewableSince: null, + exposureDuration: 0, + lastUpdateTs: !1, + elementMouseOver: !1, + exposureDurationOnClick: null, + lastAttentionBeaconSent: 0, + inViewport: !1 + }, + adserver: { + visible: !1, + viewableSince: null, + continuousCounter: 0, + exposureDuration: 0, + lastUpdateTs: !1, + lastAttentionBeaconSent: 0, + exposureDelta: 0, + inViewport: !1 + } + }; + e && this.viewability && Object.prototype.hasOwnProperty.call(this.viewability, e) ? this.viewability[e] = Object.assign({}, t[e]) : this.viewability = Object.assign({}, t), + this.startObserver(!0) + } + }, { + key: "detectBestDomElement", + value: function() { + try { + var e = window.self.document.getElementById(this.adUnitElementId) + , t = function(e) { + return Qe(e, f) + }(e) + , n = !!t + , i = function(e, t) { + return Qe(e, t) + }(e, this.prebidAdUnitConfig.sizes && this.prebidAdUnitConfig.sizes.length ? this.prebidAdUnitConfig.sizes.map((function(e) { + return e[0] + "x" + e[1] + } + )) : []) + , a = !!i + , r = i || t || e; + return { + el: r, + elId: r.id, + size: this.formatElementSize(r), + reasonNotMeasurable: t ? v : h, + hasIABDimensions: n, + hasPbjsDimensions: a + } + } catch (e) { + return Ne(e), + !1 + } + } + }, { + key: "setElement", + value: function(e) { + this.element = e + } + }, { + key: "hasMinPageExposureDuration", + value: function() { + return !!(Le() && Date.now() - Le() >= 3e4) + } + }, { + key: "hasMaxExposureDuration", + value: function() { + return "dfp" === this.options.adsrv ? this.viewability.adagio.exposureDuration > 9e4 || this.viewability.adserver.exposureDuration > 9e4 || this.viewability.adagio.exposureDuration > 6e4 && this.viewability.adserver.exposureDuration > 6e4 : this.viewability.adagio.exposureDuration > 6e4 + } + }, { + key: "formatElementSize", + value: function(e) { + try { + var t = e.getBoundingClientRect(); + return [Math.round(t.width), Math.round(t.height)].join("x") + } catch (e) { + return "0x0" + } + } + }, { + key: "startBackgroundDetection", + value: function() { + if (this.element.el && !this.element.hasIABDimensions && !this.element.hasPbjsDimensions && this.options && [0, "0"].includes(this.options.adsrv_empty) && Date.now() <= this.initTime + 4e3) { + var e = this; + return setTimeout((function() { + var t = e.detectBestDomElement(); + t && (t.hasIABDimensions || t.hasPbjsDimensions) && e.resetWithElement(t), + e.startBackgroundDetection() + } + ), 50), + !1 + } + } + }, { + key: "start", + value: function() { + if (this.startTime = Date.now(), + "function" != typeof window.CustomEvent) + return this.stop("abort"), + !1; + this.sendBeacon({ + becauseOf: "start" + }), + this.bindMouseListeners(this.element.el), + this.bindClickListener(), + this.bindMeasureEvents(), + this.w.document.dispatchEvent(new CustomEvent("adagio.measure.afterStart",{ + detail: { + measure: this + } + })) + } + }, { + key: "stop", + value: function(e) { + e = e || "stop", + this.unbindAdserverEvents(), + this.unbindMeasureEvents(), + this.unbindClickListener(), + this.element.el && this.unbindMouseListeners(this.element.el), + this.unbindBeaconEvents(), + this.resetThrottledBeacon(), + this.sendBeacon({ + becauseOf: e + }), + this.sendBeacon = function() {} + } + }, { + key: "resetWithElement", + value: function(e) { + Pe("Reset measurer for ".concat(this.adUnitElementId, " with this element"), e), + this.resetCounter++, + this.resetTime = Date.now(), + this.element.el && this.unbindMouseListeners(this.element.el), + this.setElement(e), + this.bindMouseListeners(this.element.el), + this.resetViewability(), + this.sendBeacon({ + becauseOf: "reset" + }) + } + }, { + key: "startObserver", + value: function() { + var e = this + , t = arguments.length > 0 && void 0 !== arguments[0] && arguments[0] + , n = this; + this.observer && !t || (this.observer = new IntersectionObserver((function(t) { + t.forEach((function(t) { + t.isIntersecting ? n.viewability.adagio.inViewport = !0 : (n.viewability.adagio.inViewport = !1, + document.dispatchEvent(new CustomEvent("adagio.measure.onUpdateExposureDuration",{ + detail: { + elementId: e._adUnitElementId, + measurer: "adagio", + viewability: n.viewability.adagio + } + }))) + } + )) + } + ),{ + threshold: [.49, .5, .51] + }), + this.observer.observe(window.document.getElementById(this._adUnitElementId))) + } + }, { + key: "clickListener", + value: function() { + var e = this.viewability.adagio; + try { + if (document.activeElement instanceof HTMLIFrameElement) { + var t = this.element.el.getBoundingClientRect() + , n = document.activeElement.getBoundingClientRect() + , i = !(t.right < n.left || t.left > n.right || t.bottom < n.top || t.top > n.bottom) + , a = this.element.el; + a instanceof HTMLIFrameElement || (a = a.querySelector("iframe")), + (document.activeElement === a || e.elementMouseOver || i) && null == e.exposureDurationOnClick && (e.exposureDurationOnClick = e.exposureDuration, + Pe("Click event detected on ".concat(this.element.elId, " :"), this)) + } + } catch (e) { + Ne(e) + } + } + }, { + key: "bindClickListener", + value: function() { + var e = this.params.adUnitCode || !1; + if (e && this.measurersManager) + try { + this.measurersManager.get(e) ? ke("Measure: click: clickListener already bound for adUnitCode ".concat(e)) : window.addEventListener("blur", this.clickListenerHandler, !1) + } catch (e) { + Ne(e) + } + else + Ne("Measure: click: unable to bind clickListener") + } + }, { + key: "unbindClickListener", + value: function() { + window.removeEventListener("blur", this.clickListenerHandler, !1) + } + }, { + key: "mouseHoverListener", + value: function() { + this.viewability.adagio.elementMouseOver = !0 + } + }, { + key: "mouseOutListener", + value: function() { + this.viewability.adagio.elementMouseOver = !1 + } + }, { + key: "bindMouseListeners", + value: function(e) { + e && (e.addEventListener("mouseover", this.mouseHoverListenerHandler), + e.addEventListener("mouseout", this.mouseOutListenerHandler)) + } + }, { + key: "unbindMouseListeners", + value: function(e) { + e && (e.removeEventListener("mouseover", this.mouseHoverListenerHandler), + e.removeEventListener("mouseout", this.mouseOutListenerHandler)) + } + }, { + key: "bindAdserverEvents", + value: function() { + this.unbindAdserverEvents(), + this.w.document.addEventListener("adagio.gpt.impressionViewable", this.gptImpressionViewable, !1), + this.w.document.addEventListener("adagio.gpt.slotVisibilityChanged", this.gptSlotVisibilityChanged, !1) + } + }, { + key: "unbindAdserverEvents", + value: function() { + this.w.document.removeEventListener("adagio.gpt.impressionViewable", this.gptImpressionViewable), + this.w.document.removeEventListener("adagio.gpt.slotVisibilityChanged", this.gptSlotVisibilityChanged) + } + }, { + key: "bindMeasureEvents", + value: function() { + var e = this; + this.startObserver(), + this.intervalId = setInterval((function() { + document.hidden ? Object.keys(e.viewability).forEach((function(t) { + "adagio" !== t && "dfp" !== e.options.adsrv || e.resetPreViewability(t) + } + )) : ("dfp" === e.options.adsrv && e.updateActiveViewViewability(), + e.updateViewabilityWithIntersectionObserver()) + } + ), 50) + } + }, { + key: "unbindMeasureEvents", + value: function() { + clearInterval(this.intervalId) + } + }, { + key: "bindBeaconEvents", + value: function() { + this.w.document.addEventListener("visibilitychange", Xe.bind(this), !1), + this.w.document.addEventListener("visibilitychange", Ye.bind(this), !1) + } + }, { + key: "unbindBeaconEvents", + value: function() { + this.w.document.removeEventListener("visibilitychange", Xe), + this.w.document.removeEventListener("visibilitychange", Ye) + } + }, { + key: "setBeaconTimeout", + value: function(e) { + var t = this; + e = e || "adagio"; + var n = this.params.organizationId + , i = this.viewability[e]; + this.avwBeaconTimeoutId || i.visible && i.exposureDuration - i.lastAttentionBeaconSent > 1e3 && !this.hasMaxExposureDuration() && (this.avwBeaconTimeoutId = setTimeout((function() { + -1 === at.indexOf(n) && t.sendBeacon({ + becauseOf: "exp_chg" + }), + t.avwBeaconTimeoutId = !1, + i.lastAttentionBeaconSent = i.exposureDuration + } + ), 5e3)) + } + }, { + key: "updateViewability", + value: function() { + var e = Date.now() + , t = this.viewability.adagio; + t.visible || t.continuousCounter >= 1e3 && (t.visible = !0, + t.viewableSince = e, + Pe("Adagio impressionViewable: ".concat(this._adUnitElementId)), + this.sendBeacon({ + becauseOf: "vsbl" + })), + this.updateExposureDuration(e, "adagio"), + t.lastUpdateTs = e, + this.setBeaconTimeout("adagio"), + this.w.document.dispatchEvent(new CustomEvent("adagio.measure.onUpdateViewability",{ + detail: { + measure: this + } + })), + !1 === this.refreshStarted && !1 === this.doNotRefresh && this.refresh() + } + }, { + key: "updateViewabilityWithIntersectionObserver", + value: function() { + var e = Date.now() + , t = this.viewability.adagio; + if (!t.visible && t.continuousCounter >= 1e3) { + t.visible = !0, + t.viewableSince = e; + var n = new Date(t.viewableSince).toString(); + Pe("Adagio impressionViewable with IObsrv: ".concat(this.element.elId, " at ").concat(n)), + this.sendBeacon({ + becauseOf: "vsbl" + }) + } + t.inViewport ? (this.updateExposureDuration(e, "adagio"), + t.lastUpdateTs = e, + this.setBeaconTimeout("adagio"), + !1 === this.refreshStarted && !1 === this.doNotRefresh && this.refresh()) : this.resetPreViewability("adagio") + } + }, { + key: "updateActiveViewViewability", + value: function() { + var e = Date.now() + , t = this.viewability.adserver; + t.inViewport ? (this.updateExposureDuration(e, "adserver"), + t.lastUpdateTs = e, + !1 === this.refreshStarted && !1 === this.doNotRefresh && this.refresh("adserver"), + this.setBeaconTimeout("adserver")) : this.resetPreViewability("adserver") + } + }, { + key: "refresh", + value: function(e) { + e = e || "adagio"; + var t = this.params.adUnitCode || !1; + if (t) { + var n = !(!this.w.ADAGIO.adUnits || !this.w.ADAGIO.adUnits[t]) && this.w.ADAGIO.adUnits[t].printNumber + , i = this.viewability[e]; + if (!n) + return Pe("No PrintNumber to start refresh for this adUnit: ".concat(t)), + void (this.doNotRefresh = !0); + if (n && n >= this.refreshConfig.maxRefresh) + return Pe("Stoping refresh because currentPrintNumber is >= of ".concat(this.refreshConfig.maxRefresh, " maxRefresh for this adUnit: ").concat(t)), + void (this.doNotRefresh = !0); + if ((1 !== n || this.hasMinPageExposureDuration()) && i.continuousCounter >= this.refreshConfig.timeToRefresh && n <= this.refreshConfig.maxRefresh) { + var a = (dt() || []).concat(this.w.ADAGIO.doNotRefresh || []); + if (Array.isArray(a) && -1 !== a.indexOf("*")) + return Pe("No Refresh because doNotRefresh is activate on the whole page"), + this.doNotRefresh = !0, + !1; + if (Array.isArray(a) && t && -1 !== a.indexOf(t)) + return Pe("This adUnitCode is in the ADAGIO.doNotRefresh: ".concat(t)), + this.doNotRefresh = !0, + !1; + this.refreshStarted = !0, + Pe("Trying to refresh adUnitCode: " + t + " at " + i.continuousCounter + " with conf: ", this.refreshConfig), + function(e) { + var t = e.adUnitCode + , n = e.adUnitElementId + , i = e.refreshConfig + , a = e.currentPrintNumber + , r = new CustomEvent("adagio.refresh.onBeforeRefresh",{ + detail: { + adUnitCode: t, + adUnitElementId: n, + refreshConfig: i, + currentPrintNumber: a + }, + cancelable: !0 + }); + Ce().document.dispatchEvent(r) ? "dfp" === i.adServer ? et(e, nt, it) : "sas" === i.adServer ? tt(e, nt) : Pe("No refresher has been defined") : Pe("Refresh is handled by the publisher itself") + }({ + adUnitCode: t, + adUnitElementId: this.adUnitElementId, + refreshConfig: this.refreshConfig, + currentPrintNumber: n + }) + } + } else + Pe("No adUnitCode to start refresh for this adUnit: ".concat(t)) + } + }, { + key: "updateExposureDuration", + value: function(e, t) { + t = t || "adagio"; + var n = this.viewability[t]; + if (!n.lastUpdateTs) + return 0; + var i = e - n.lastUpdateTs; + n.exposureDuration += i, + n.continuousCounter += i + } + }, { + key: "resetPreViewability", + value: function(e) { + e = e || "adagio"; + var t = this.viewability[e]; + t.lastUpdateTs = !1, + t.visible || (t.continuousCounter = 0), + this.w.document.dispatchEvent(new CustomEvent("adagio.measure.onResetPreViewability",{ + detail: { + measure: this + } + })) + } + }, { + key: "getFeatures", + value: function(e) { + var t; + return (t = this.featuresManager.get(e)) ? Pe("Freezed features(v".concat(t.version, ") for ").concat(e, " from adagioBidAdapter:"), t.features) : Pe("No features found for ".concat(e)), + t + } + }, { + key: "sendThrottledBeacon", + value: function() { + var e = this.beaconsQueue.pop(); + this.resetThrottledBeacon(), + e && (e.throttled = !0, + this.sendBeacon(e)) + } + }, { + key: "resetThrottledBeacon", + value: function() { + this.beaconsQueue = [], + this.beaconsPending = !1 + } + }, { + key: "sendBeacon", + value: function(e) { + var t = this + , n = this + , i = (e = e || {}).events || []; + return !!this.navigationFeatures && (!!this.navigationFeatures.allowBeaconSending("avw") && (this.throttleBeacons && -1 !== ["start", "reset", "vsbl", "vsbl_actvw"].indexOf(e.becauseOf) && !e.throttled ? this.beaconsPending ? void this.beaconsQueue.push(e) : (this.beaconsPending = !0, + this.beaconsQueue.push(e), + void setTimeout(this.sendThrottledBeacon.bind(this), 3e3)) : void Je({ + collector: "avw", + data: function() { + e = e || {}; + var i = Date.now() + , a = Le() + , r = 0; + try { + r = (new Date).getTimezoneOffset() + } catch (e) {} + var s = []; + n.prebidAdUnitConfig && Array.isArray(n.prebidAdUnitConfig.sizes) && n.prebidAdUnitConfig.sizes.map((function(e) { + return s.push(e.join("x")) + } + )); + var o = { + pv_id: t.w.ADAGIO && t.w.ADAGIO.pageviewId ? t.w.ADAGIO.pageviewId : "", + adu_el_id: n.adUnitElementId, + v: n.beaconVersion++, + tz_off: r, + evt: e.becauseOf || "", + js_late: t.w.ADAGIO && !0 === t.w.ADAGIO.late ? 1 : 0, + js_ts: t.w._ADAGIO && t.w._ADAGIO.adagioStartTime ? t.w._ADAGIO.adagioStartTime : "", + size: n.element.size, + pbjs_sizes: s.join(","), + is_pbjs_size: !0 === n.element.hasPbjsDimensions ? 1 : 0, + is_iab_size: !0 === n.element.hasIABDimensions ? 1 : 0, + msrbl: !0 === n.measurable ? 1 : 0, + adu_exp: n.viewability.adagio.exposureDuration, + pg_durat: a ? i - a : 0, + pg_paused: n.pageVisibility.computedDuration, + pg_exp: a ? i - a - n.pageVisibility.computedDuration : 0, + vsbl: !0 === n.viewability.adagio.visible ? 1 : 0, + adsrv_vsbl: !0 === n.viewability.adserver.visible ? 1 : 0, + adsrv_att_delta: n.viewability.adserver.exposureDelta, + clk_time: null != n.viewability.adagio.exposureDurationOnClick ? n.viewability.adagio.exposureDurationOnClick : "", + reset: n.resetCounter, + adsrv_adu_exp: n.viewability.adserver.exposureDuration, + navs_ts: null != n.navigationStart ? n.navigationStart : "", + trgr_ts: null != n.ts ? parseInt(n.ts, 10) : "", + init_ts: n.initTime, + start_ts: n.startTime, + reset_ts: null != n.resetTime ? n.resetTime : "", + vsbl_ts: null != n.viewability.adagio.viewableSince ? n.viewability.adagio.viewableSince : "", + adsrv_vsbl_ts: null != n.viewability.adserver.viewableSince ? n.viewability.adserver.viewableSince : "", + auct_id: n.auctionId ? n.auctionId : "" + } + , d = {}; + for (var u in n.params) { + if (Object.prototype.hasOwnProperty.call(st, u)) + d[st[u]] = n.params[u] ? n.params[u] : "" + } + var c = { + featv: n.featuresVersion + }; + for (var l in n.features) { + if (Object.prototype.hasOwnProperty.call(rt, l)) + c[rt[l]] = n.features[l]; + else + c[l] = n.features[l] + } + c.pn || (c.pn = 1); + var f = { + sess_lngth: t.navigationFeatures.sessionLength, + avg_sess_lngth: t.navigationFeatures.avgSessionLength, + sess_cnt: t.navigationFeatures.totalSessions, + rfr_fqdn: t.navigationFeatures.referrerFQDN, + prv_pgtyp: t.navigationFeatures.previousPagetype + } + , v = {}; + for (var h in n.options) { + if (Object.prototype.hasOwnProperty.call(ot, h)) + v[ot[h]] = n.options[h] ? n.options[h] : "" + } + return Object.assign(o, c, f, d, v), + n.element.reasonNotMeasurable, + o + }, + events: i + }))) + } + }]) + }() + , ct = new RegExp(/(^adagio$|[_-]adagio$|^adagio[_-])/i) + , lt = new RegExp(/^[a-zA-Z0-9-_]{1,50}$/) + , ft = new RegExp(/^[0-9]{4}$/) + , vt = function(e) { + var t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {} + , n = "adagio" === ("" + t[e]).toLowerCase(); + return n || ct.test(e) + } + , ht = function(e, t) { + var n = !1; + if (e && e.length) { + var i = e.filter((function(e) { + return e.code === t + } + )); + i.length && (n = i[0]) + } + return n + } + , pt = function(e) { + return e.localPbjsRef && e.localPbjsRef.aliasRegistry + } + , gt = function() { + var e = Ce(); + if (!e) + return []; + e.ADAGIO = e.ADAGIO || {}; + var t = void 0 + , n = function(e) { + t = Array.isArray(t) ? t.concat(e) : e + }; + e.ADAGIO.pbjsAdUnits && (t = e.ADAGIO.pbjsAdUnits), + e.ADAGIO.rtbpbjsAdUnits && n(e.ADAGIO.rtbpbjsAdUnits), + e.ADAGIO.pbjsWtgAdUnits && n(e.ADAGIO.pbjsWtgAdUnits); + var i = Te(); + return t.forEach((function(e) { + try { + e.bids[0].params.environment || (e.bids[0].params.environment = i) + } catch (e) { + ke(e) + } + } + )), + t + } + , mt = function() { + return o((function e() { + r(this, e), + this.measurers = {}, + this.init() + } + ), [{ + key: "init", + value: function() {} + }, { + key: "store", + value: function(e) { + var t = e.params && e.params.adUnitCode ? e.params.adUnitCode : void 0; + if (this.get(t)) + return !1; + this.measurers[t] = e + } + }, { + key: "get", + value: function(e) { + return e ? this.measurers[e] : this.measurers + } + }, { + key: "has", + value: function(e, t) { + var n = this.get(e); + return n && t ? n.auctionId === t : !!n + } + }, { + key: "getByAdUnitElementId", + value: function(e) { + var t = this + , n = Object.keys(this.measurers); + return !(!n || !n.length) && n.filter((function(n) { + return t.measurers[n] && t.measurers[n]._adUnitElementId === e + } + )) + } + }, { + key: "remove", + value: function(e) { + return delete this.measurers[e] + } + }]) + }(); + function bt() { + var e = Ce() + , t = e.navigator.userAgent + , n = t.toLowerCase(); + return /Edge\/\d./i.test(t) ? "edge" : n.indexOf("chrome") > 0 ? "chrome" : n.indexOf("firefox") > 0 ? "firefox" : n.indexOf("safari") > 0 ? "safari" : n.indexOf("opera") > 0 ? "opera" : n.indexOf("msie") > 0 || e.MSStream ? "ie" : "unknow" + } + var yt = function() { + return o((function e() { + r(this, e), + this._featuresByAdUnitElementId = {}, + this.init() + } + ), [{ + key: "init", + value: function() {} + }, { + key: "storeLegacy", + value: function(e, t, n) { + if ("object" !== a(t) || this.get(e) && !n) + return !1; + this._featuresByAdUnitElementId[e] = t + } + }, { + key: "store", + value: function(e, t) { + var i = e.adUnitCode + , r = e.features + , s = e.params; + if (s.adUnitElementId) { + if ("object" !== a(r) || this.get(s.adUnitElementId) && !t) + return ke("Features cannot be set. data.features is not an object. adUnitCode: ".concat(i)), + !1; + var o = ""; + Ue() && (o = Ce().location.href || ""); + var d, u, c = n(n({}, r), { + device: (u = Ce().navigator.userAgent, + /(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(u) ? 5 : /Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile|Kindle|NetFront|Silk-Accelerated|(hpw|web)OS|Fennec|Minimo|Opera M(obi|ini)|Blazer|Dolfin|Dolphin|Skyfire|Zune/.test(u) ? 4 : 2).toString(), + os: (d = Ce().navigator.userAgent.toLowerCase(), + d.indexOf("android") > 0 ? "android" : d.indexOf("iphone") > 0 ? "ios" : d.indexOf("linux") > 0 ? "linux" : d.indexOf("mac") > 0 ? "mac" : d.indexOf("win") > 0 ? "windows" : ""), + browser: bt(), + url: o + }); + this._featuresByAdUnitElementId[s.adUnitElementId] = { + version: "_", + features: c + } + } else + ke("Features cannot be set. Missing adUnitElementId. adUnitCode: ".concat(i)) + } + }, { + key: "get", + value: function(e) { + return e ? this._featuresByAdUnitElementId[e] : this._featuresByAdUnitElementId + } + }]) + }() + , At = [["rubicon", "secure-assets.rubiconproject.com"], ["pubmatic", "ads.pubmatic.com"], ["improvedigital", "ice.360yield.com"], ["onetag", "onetag-sys.com"], ["indexexchange", "ssum-sec.casalemedia.com"], ["richaudience", "sync.richaudience.com"], ["33across", "ssc-cms.33across.com"], ["appnexus", "ib.adnxs.com"], ["smart", "ssbsync.smartadserver.com"], ["adyoulike", "visitor.omnitagjs.com"], ["sovrn", "ap.lijit.com"], ["freewheel", "ads.stickyadstv.com"], ["openx", "u.openx.net"], ["openxpbs", "u.openx.net"], ["triplelift", "eb2.3lift.com"], ["eplanning", "ads.us.e-planning.net"], ["unruly", "sync.1rx.io"]] + , wt = []; + e.adagioStartTime = Date.now(); + var It = function(e, t) { + var n = Ce() + , i = new RegExp(/^(<\/iframe>)(?![<])$/i) + , a = new RegExp(/^()(?![<])$/i) + , r = n.document.getElementsByTagName("body")[0] + , s = i.test(e.html) + , o = a.test(e.html); + return e.html && (s || o) ? (setTimeout((function() { + r.insertAdjacentHTML("beforeend", e.html) + } + ), t), + !0) : (Pe("userSyncing: html markup is not valid to be added to the DOM."), + !1) + } + , _t = function(e) { + var t = new Se + , n = bt() + , i = t.get("syncs") || {} + , a = u(At); + Ce().ADAGIO && Ce().ADAGIO.bdrSyncs && Ce().ADAGIO.bdrSyncs.length && a.push.apply(a, u(Ce().ADAGIO.bdrSyncs)); + var r = Date.now() + , s = 0; + e && Array.isArray(e.user_syncs) && e.user_syncs.forEach((function(e) { + var o = 1e3 * s; + if ("safari" === n) { + Pe("userSyncing: enter in Safari mode"); + var d = function(e, t) { + var n = new RegExp(/src=["'](https?:\/\/[^'"]+)["']/) + , i = e.match(n); + i[1] || ke("userSyncing: unable to find src in markup"); + var a = Be(i[1]); + return t.filter((function(e) { + return e[1] === a.hostname + } + )) + }(e.html, a); + if (!d || !d.length) + return void ke("userSyncing: no bidder found", e.html); + d.forEach((function(n) { + var a = n[0]; + if (Object.prototype.hasOwnProperty.call(i, a)) { + var d = i[a]; + r >= d + 216e5 && It(e, o) && (i[a] = r, + t.store("syncs", i), + s++) + } else + It(e, o) && (i[a] = r, + t.store("syncs", i), + s++) + } + )) + } else + It(e, o), + s++ + } + )) + } + , Ot = function(e, t, n, i, a) { + var r = gt() + , s = e.options || {}; + if (e.adUnitCode && r) { + var o = Ce() + , d = o.ADAGIO.adUnits && o.ADAGIO.adUnits[e.adUnitCode] && o.ADAGIO.adUnits[e.adUnitCode].auctionId; + if (!d) + return ke("Cannot start measurer.", "".concat(e.adUnitCode, ": no auctionId config found in window.ADAGIO.adUnits")), + !1; + if (o.ADAGIO.adUnits[e.adUnitCode].pageviewId !== o.ADAGIO.pageviewId) + return ke("Cannot start measurer.", "".concat(e.adUnitCode, ": non consistent pageviewId for window.ADAGIO.adUnits")), + !1; + if (i.has(e.adUnitCode, d)) + return ke("Cannot start measurer.", "".concat(e.adUnitCode, ": a measurer is already registred for current auction ").concat(d)), + i.get(e.adUnitCode).stop(), + i.remove(e.adUnitCode), + !1; + var u = i.get(e.adUnitCode); + if (u && u.auctionId !== d && (u.stop(), + i.remove(e.adUnitCode)), + !u && e.adUnitElementId) { + var c = i.getByAdUnitElementId(e.adUnitElementId); + c && c.map((function(e) { + var t = i.get(e); + t && (t.stop(), + i.remove(e)) + } + )) + } + var l = function(e, t) { + var n = ht(e, t) + , i = !1; + if (n) { + var a = pt(n) + , r = n.bids.filter((function(e) { + return vt(e.bidder, a) + } + )); + r.length && (i = r[0]) + } + return i + }(r, e.adUnitCode); + if (!l) + return ke("Cannot start measurer.", "".concat(e.adUnitCode, ": adagio bidder has not been set (see prebid.js)")), + !1; + var f = function(e) { + if (!e || !e.params || !e.params.adUnitElementId) + return !1; + var t = e.params; + return { + adUnitElementId: t.adUnitElementId, + category: t.category || "", + environment: t.environment || "", + organizationId: t.organizationId || "", + pagetype: t.pagetype || "", + placement: t.placement || "", + site: t.site || "", + size: t.size || "", + subcategory: t.subcategory || "", + postBid: t.postBid || !1 + } + }(l); + if (!f) + return ke("Cannot start measurer.", "".concat(e.adUnitCode, ": no params. Mainly due to lack of adUnitElementId in Adagio bidder params")), + !1; + if (!function(e) { + var t = e || {} + , n = t.organizationId + , i = t.site; + return !(!ft.test(n) || !lt.test(i)) + }(f = Object.assign({}, f, { + adUnitCode: e.adUnitCode + }))) + return ke("Cannot start measurer.", "".concat(e.adUnitCode, ': invalid "site" or "organizationId" in Prebid.js params')), + !1; + var v = ht(r, e.adUnitCode); + f.organizationId && f.site && (s.refresh = wt.find((function(t) { + return t.adUnitCode == e.adUnitCode + } + ))), + s.refresh && (s.adsrv ? s.refresh.adServer = s.adsrv : ke("Cannot detect adserver.")), + v && (s.adUnitConfig = v), + t && (s._window = t); + var h = f.adUnitElementId; + new ut({ + ts: e.ts, + adUnitElementId: h, + auctionId: d, + params: f, + options: s, + featuresManager: n, + measurersManager: i, + navigationFeatures: a + }) + } else + ke("Cannot start measurer.", { + msg: "no adUnitCode or adUnitsArray" + }) + } + , Dt = function(e, t, n, i, r) { + var s = t.adsrv + , o = t.action + , d = t.eventName + , u = t.afterOptions; + if (e.action !== o || !e.data.eventName) + return !1; + if (e.data.eventName === d) { + var c = e.data.args.elementId + , l = gt(); + if (!l) + return void Pe("".concat(s, ": Could not find ADAGIO.pbjsAdUnits or pbjs.adUnits before starting a measurer for ").concat(c)); + var f = l.filter((function(e) { + var t = pt(e); + return e.bids.find((function(e) { + return vt(e.bidder, t) && e.params.adUnitElementId === c + } + )) + } + )); + if (!f || !f.length) + return void Pe("".concat(s, ": Could not find adUnitCode from adUnitElementId ").concat(c)); + f = f.pop().code, + Pe("Start a measurer from ".concat(s, ".").concat(d, " event for adUnitCode ").concat(f, ". Delay from queued: ").concat(Date.now() - e.ts)); + var v = { + adsrv: s, + adsrv_crea_id: e.data.args.creativeId, + adsrv_empty: !0 === e.data.args.isEmpty ? "1" : "0", + adsrv_size: function(e) { + if (Array.isArray(e)) { + var t = ["string", "number"]; + if (2 === e.length && (t.indexOf(a(e[0])) > 0 || t.indexOf(a(e[1])) > 0)) + return e.join("x") + } + if ("string" == typeof e && new RegExp(/^\d{1,4}x\d{1,4}$/).test(e)) + return e; + return "" + }(e.data.args.creativeSize) + }; + "function" == typeof u && u(e, v), + Ot({ + ts: e.ts, + adUnitCode: f, + adUnitElementId: c, + options: v + }, e.data._window, n, i, r) + } + } + , Et = function(t, i) { + var r = i.navigationFeatures + , s = i.adagioNavigation + , o = i.featuresManager + , d = i.measurersManager + , u = Ce(); + if (u._ADAGIO = u._ADAGIO || {}, + je() && "pb-analytics-event" !== t.action && (u._ADAGIO.queueHistory = u._ADAGIO.queueHistory || [], + u._ADAGIO.queueHistory.push(t), + u._ADAGIO.queueHistory.length > 100 && u._ADAGIO.queueHistory.shift()), + "object" !== a(t) || !t.action) + return !1; + if ("ssp-data" === t.action) { + !function(e) { + e && (e.ssp_version || e.sspVersion) && (Ce().ADAGIO.versions.ssp = e.ssp_version || e.sspVersion) + }(t.data), + function(e, t) { + try { + if (e && "number" == typeof e.vwSmplgNxt) { + var n = Ce(); + t.setSampling(n.ADAGIO.pageviewId, { + vwSmplgNxt: e.vwSmplgNxt + }), + t.setVwSamplingNext(e.vwSmplgNxt) + } + } catch (e) { + Pe(e) + } + }(t.data, s), + _t(t.data), + function(e) { + if (!e || !e.refresh) + return !1; + var t = Ce(); + e.refresh.map((function(e) { + t.ADAGIO.adUnits && t.ADAGIO.adUnits[e.adUnitCode] && !t.ADAGIO.adUnits[e.adUnitCode].printNumber && (t.ADAGIO.adUnits[e.adUnitCode].printNumber = 1), + void 0 === e.adServer && (e.timeToRefresh = 1e3 * e.timeToRefresh), + wt.push(e) + } + )) + }(t.data); + var c = gt(); + if (c && c.length && Array.isArray(c[0].bids)) { + var l = pt(c[0]) + , f = c[0].bids.find((function(e) { + return vt(e.bidder, l) + } + )); + f && f.params && f.params.pagetype && (r.pageType = f.params.pagetype) + } + } else if ("features" === t.action) + t.data.adUnitCode ? o.store(t.data, !0) : Object.keys(t.data).map((function(e) { + o.storeLegacy(e, t.data[e], !0) + } + )); + else if ("store" === t.action) { + if (!t.data) + return void Ne("store action called without ob.data"); + Pe("store action called with data:", t.data); + var v = t.data.bidderRequestsCount ? t.data.bidderRequestsCount.toString() : "1" + , h = t.data + , p = h.organizationId + , g = h.site + , m = h.adUnitCode + , b = h.ortb2 + , y = void 0 === b ? {} : b + , A = h.ortb2Imp + , w = void 0 === A ? {} : A + , I = h.params + , _ = void 0 === I ? {} : I + , O = h.mediaTypes + , D = void 0 === O ? {} : O + , E = h.localPbjs + , S = h.localPbjsRef + , U = y.adg_rtd || {} + , C = n(n(n({}, U.features || {}), w.adg_rtd), {}, { + print_number: v + }); + o.store({ + features: C, + params: { + adUnitElementId: w.divId + }, + adUnitCode: t.data.adUnitCode + }, !0); + var x = new Set + , G = D.banner + , j = D.video; + G && Array.isArray(G.sizes) && G.sizes.length && (Array.isArray(G.sizes[0]) ? G.sizes.forEach((function(e) { + return x.add(e) + } + )) : x.add([G.sizes])), + j && Array.isArray(j.playerSize) && j.playerSize.length && x.add(j.playerSize[0]), + u.ADAGIO.pbjsAdUnits = u.ADAGIO.pbjsAdUnits || []; + var k = u.ADAGIO.pbjsAdUnits.filter((function(e) { + return e.code !== t.data.adUnitCode + } + )); + k.push({ + code: m, + mediaTypes: D, + sizes: Array.from(x), + bids: [{ + bidder: "adagio", + params: { + organizationId: p, + site: g, + pagetype: y.pagetype, + category: y.category, + placement: w.placement, + adUnitElementId: w.divId + } + }], + auctionId: U.uid || _.adagioAuctionId, + pageviewId: u.ADAGIO.pageviewId, + printNumber: v, + localPbjs: E, + localPbjsRef: S + }), + u.ADAGIO.pbjsAdUnits = k, + u.ADAGIO.adUnits[m] = { + auctionId: U.uid || _.adagioAuctionId, + pageviewId: u.ADAGIO.pageviewId, + printNumber: v + } + } else if ("gpt-event" === t.action) + !function(e, t, n, i) { + if ("gpt-event" !== e.action || !e.data.eventName) + return !1; + var a = Ce(); + if ("slotRenderEnded" === e.data.eventName) { + var r = e.data.args + , s = r.advertiserId + , o = r.campaignId + , d = r.creativeId + , u = r.isEmpty + , c = r.lineItemId + , l = r.size + , f = r.slot + , v = f.getSlotElementId() + , h = gt(); + if (!h) + return void Pe("DFP: Could not find ADAGIO.pbjsAdUnits or pbjs.adUnits before starting a measurer for ".concat(v)); + var p = h.filter((function(e) { + var t = pt(e); + return e.bids.find((function(e) { + return vt(e.bidder, t) && e.params.adUnitElementId === v + } + )) + } + )); + if (p.length > 0) { + if (p[0].bids.length && p[0].bids[0].params && -1 !== [1013, "1013", 1026, "1026", 1090, "1090"].indexOf(p[0].bids[0].params.organizationId)) + return void Pe("DFP: by-pass due to organizationId exception"); + p = p[0].code + } else { + if (!(a.ADAGIO && a.ADAGIO.pbjsAdUnits && a.ADAGIO.pbjsAdUnits.length)) + return void Pe("DFP: Cannot start measurer", "Could not find adUnitCode from adUnitElementId (2): ".concat(v)); + var g = f.getAdUnitPath() + , m = a.ADAGIO.pbjsAdUnits.find((function(e) { + return e.code === g || e.code === v + } + )); + if (!m) + return void Pe("DFP: Cannot start measurer", "Could not find adUnitCode from adUnitElementId (1): ".concat(v)); + Pe("DFP: auto-detect prebid adunit", "update ADAGIO.pbjsAdUnit array with adUnitElementId: ".concat(v)), + m.bids[0].params.adUnitElementId = v, + p = m.code + } + Pe("Start a measurer from gpt.slotRenderEnded event for ".concat(v, ". Delay from queued: ").concat(Date.now() - e.ts)), + Ot({ + ts: e.ts, + adUnitCode: p, + options: { + adsrv: "dfp", + adsrv_advrt_id: s, + adsrv_cmpgn_id: o, + adsrv_crea_id: d, + adsrv_empty: !0 === u ? "1" : "0", + adsrv_lnitem_id: c, + adsrv_size: l && "undefined" !== l.join ? l.join("x") : "" + } + }, e.data._window, t, n, i) + } + "function" == typeof window.CustomEvent && a.document.dispatchEvent(new CustomEvent("adagio.gpt." + e.data.eventName,{ + detail: e + })) + }(t, o, d, r); + else if ("adagio-hb-event" === t.action) + Dt(t, { + adsrv: "hbagency", + action: "adagio-hb-event", + eventName: "renderEvent" + }, o, d, r); + else if ("adagio-avs-render" === t.action) + Dt(t, { + adsrv: "thm", + action: "adagio-avs-render", + eventName: "renderEvent" + }, o, d, r); + else if ("adagio-my-adserver-events" === t.action) + Dt(t, { + adsrv: "default", + action: "adagio-my-adserver-events", + eventName: "renderEvent" + }, o, d, r); + else if ("splcznsci-event" === t.action) + Dt(t, { + adsrv: "splcznsci", + action: "splcznsci-event", + eventName: "renderEvent" + }, o, d, r); + else if ("adagio-creative-rendered" === t.action) + Dt(t, { + adsrv: "sovrn", + action: "adagio-creative-rendered", + eventName: "renderEvent" + }, o, d, r); + else if ("adagio-hbPROJECT-event" === t.action) + Dt(t, { + adsrv: "hbproject", + action: "adagio-hbPROJECT-event", + eventName: "renderEvent" + }, o, d, r); + else if ("skyboard-event" === t.action) + Dt(t, { + adsrv: "skyboard", + action: "skyboard-event", + eventName: "renderEvent" + }, o, d, r); + else if ("purpleads-events" === t.action) + Dt(t, { + adsrv: "purpleads", + action: "purpleads-events", + eventName: "renderEvent" + }, o, d, r); + else if ("sas-event" === t.action) + !function(e, t, n, i) { + if ("sas-event" !== e.action || !e.data.eventName) + return !1; + var a = Ce(); + if ("noad" === e.data.eventName) { + var r = e.data.args.formatId + , s = gt(); + if (!s) + return void Pe("SAS: Could not find ADAGIO.pbjsAdUnits or pbjs.adUnits before starting a measurer for ".concat(r)); + s.filter((function(t) { + return t.code === e.data.args.tagId + } + )).length && (r = e.data.args.tagId), + r = "number" == typeof r ? r.toString() : r, + a.ADAGIO.adUnits && a.ADAGIO.adUnits[r] && (a.ADAGIO.adUnits[r].sasNoad = !0) + } + if ("setHeaderBiddingWinner" === e.data.eventName) { + var o = e.data.args.formatId + , d = gt(); + if (!d) + return void Pe("SAS: Could not find ADAGIO.pbjsAdUnits or pbjs.adUnits before starting a measurer for ".concat(o)); + if (d.filter((function(t) { + return t.code === e.data.args.tagId + } + )).length) + o = e.data.args.tagId; + else { + var u = d.find((function(t) { + return !(!t.bids || !t.bids.find((function(t) { + return e.data.args.tagId === t.params.adUnitElementId + } + ))) + } + )); + u && (o = u.code) + } + o = "number" == typeof o ? o.toString() : o; + var c = !1; + a.ADAGIO.adUnits && a.ADAGIO.adUnits[o] && a.ADAGIO.adUnits[o].sasNoad && (c = !!a.ADAGIO.adUnits[o].sasNoad, + delete a.ADAGIO.adUnits[o].sasNoad), + Pe("Start a measurer from SAS", "".concat(e.data.eventName, " event for ").concat(o, ". Delay from queued: ").concat(Date.now() - e.ts)), + Ot({ + ts: e.ts, + adUnitCode: o, + options: { + adsrv: "sas", + adsrv_empty: c ? "1" : "0" + } + }, e.data._window, t, n, i) + } + "function" == typeof window.CustomEvent && a.document.dispatchEvent(new CustomEvent("adagio.sas." + e.data.eventName,{ + detail: e + })) + }(t, o, d, r); + else if ("ast-event" === t.action) + !function(e, t, n, i) { + if ("ast-event" !== e.action || !e.data.eventName) + return !1; + if ("adLoaded" === e.data.eventName && "banner" === e.data.args[0].adType) { + var a = e.data.args[0] + , r = a.targetId + , s = a.creativeId + , o = a.width + , d = a.height + , u = gt(); + if (!u) + return void Pe("AST: Could not find ADAGIO.pbjsAdUnits or pbjs.adUnits before starting a measurer for ".concat(r)); + var c = u.filter((function(e) { + var t = pt(e); + return e.bids.find((function(e) { + return vt(e.bidder, t) && e.params.adUnitElementId === r + } + )) + } + )); + if (!c.length) + return void Pe("AST: Cannot start measurer", "Could not find adUnitCode from adUnitElementId: ".concat(r)); + c = c[0].code, + Pe("Start a measurer from ast.asLoaded event for ".concat(r, ". Delay from queued: ").concat(Date.now() - e.ts)), + Ot({ + ts: e.ts, + adUnitCode: c, + adUnitElementId: r, + options: { + adsrv: "ast", + adsrv_crea_id: s, + adsrv_empty: 0, + adsrv_size: "".concat(o, "x").concat(d) + } + }, e.data._window, t, n, i) + } + }(t, o, d, r); + else if ("pb-analytics-event" === t.action) + ; + else if ("reset" === t.action) + !function(t) { + Pe("Reset action called"); + var n = Ce() + , i = t.get(); + for (var a in i) { + var r = i[a]; + r.stop(), + t.remove(r.params.adUnitCode) + } + e.adagioStartTime = Date.now(), + n.ADAGIO.pageviewId = Re() + }(d); + else if ("session" === t.action) { + var N = t.data && t.data.session ? t.data.session : {} + , P = N.rnd + , R = N.id + , T = N.new + , B = N.initiator + , L = N.v + , z = N.lastActivityTime; + try { + s.startOrUpdate({ + rnd: P, + id: R, + isNew: T, + initiator: B, + v: L, + lastActivityTime: z + }), + s.setSampling(u.ADAGIO.pageviewId) + } catch (e) { + Pe(e) + } + } else + ke('queue: Unknown action "'.concat(t.action, '" in payload ').concat(t)) + }; + !function() { + var e = Ce(); + try { + var t = document.currentScript; + t && t.id && t.id.startsWith("adagiojs-") && (Pe("remove adagioScript from localStorage"), + e.localStorage.removeItem("adagioScript")) + } catch (e) { + Ne(e) + } + e.ADAGIO && !0 === e.ADAGIO.hasRtd || function() { + var e = { + GPT: { + IMPRESSION_VIEWABLE: "impressionViewable", + SLOT_ON_LOAD: "slotOnload", + SLOT_RENDER_ENDED: "slotRenderEnded", + SLOT_REQUESTED: "slotRequested", + SLOT_RESPONSE_RECEIVED: "slotResponseReceived", + SLOT_VISIBILITY_CHANGED: "slotVisibilityChanged" + }, + SAS: { + CALL: "call", + CLEAN: "clean", + BEFORE_RENDER: "beforeRender", + CMP_ANSWERED: "CmpAnswered", + CMP_CALLED: "CmpCalled", + LOAD: "load", + NOAD: "noad", + RENDER: "render", + RESET: "reset", + AD: "ad", + SET_HEADER_BIDDING_WINNER: "setHeaderBiddingWinner" + }, + AST: { + adRequested: "adRequested", + adAvailable: "adAvailable", + adBadRequest: "adBadRequest", + adLoaded: "adLoaded", + adNoBid: "adNoBid", + adRequestFailure: "adRequestFailure", + adError: "adError", + adCollapse: "adCollapse" + } + } + , t = Ce(); + t.ADAGIO = t.ADAGIO || {}, + t.ADAGIO.windows = t.ADAGIO.windows || []; + var n = window.self + , i = t.ADAGIO.windows.find((function(e) { + return e.self === n + } + )); + i || (i = { + self: n + }, + t.ADAGIO.windows.push(i)); + try { + if (!0 === i.gpt || "gpt" === i.adserver) + return; + n.googletag = n.googletag || {}, + n.googletag.cmd = n.googletag.cmd || [], + n.googletag.cmd.push((function() { + Object.keys(e.GPT).map((function(t) { + return e.GPT[t] + } + )).forEach((function(e) { + n.googletag.pubads().addEventListener(e, (function(i) { + t.ADAGIO.queue.push({ + action: "gpt-event", + data: { + eventName: e, + args: i, + _window: n + }, + ts: Date.now() + }) + } + )) + } + )), + i.gpt = !0, + i.adserver = "gpt" + } + )) + } catch (e) {} + try { + if (!0 === i.sas || "sas" === i.adserver) + return; + n.sas = n.sas || {}, + n.sas.cmd = n.sas.cmd || [], + n.sas.cmd.push((function() { + Object.keys(e.SAS).map((function(t) { + return e.SAS[t] + } + )).forEach((function(e) { + n.sas.events.on(e, (function(i) { + t.ADAGIO.queue.push({ + action: "sas-event", + data: { + eventName: e, + args: i, + _window: n + }, + ts: Date.now() + }) + } + )) + } + )), + i.sas = !0, + i.adserver = "sas" + } + )) + } catch (e) {} + try { + if (!0 === i.ast || "ast" === i.adserver) + return; + n.apntag = n.apntag || {}, + n.apntag.anq = n.apntag.anq || [], + n.apntag.anq.push((function() { + Object.keys(e.AST).map((function(t) { + return e.AST[t] + } + )).forEach((function(e) { + n.apntag.onEvent(e, (function() { + t.ADAGIO.queue.push({ + action: "ast-event", + data: { + eventName: e, + args: arguments, + _window: n + }, + ts: Date.now() + }) + } + )) + } + )), + i.ast = !0, + i.adserver = "ast" + } + )) + } catch (e) {} + }(), + e.ADAGIO && !0 === e.ADAGIO.loaded ? Pe("adagio.js already loaded") : (!function() { + Pe("Initialize adagio.js"); + var e = Ce() + , t = new ze + , n = new Me + , i = new yt + , a = new mt; + for (e.ADAGIO = e.ADAGIO || {}, + e.ADAGIO.versions = e.ADAGIO.versions || {}, + e.ADAGIO.versions.adagiojs = "2.1.8", + e.ADAGIO.pageviewId = e.ADAGIO.pageviewId || Re(), + e.ADAGIO.features = e.ADAGIO.features || i, + e.ADAGIO.spl = e.ADAGIO.spl || {}, + e.ADAGIO.spl.avw = e.ADAGIO.spl.avw || !1, + e.ADAGIO.spl.bids = e.ADAGIO.spl.bids || !1, + e.ADAGIO.queue = e.ADAGIO.queue || [], + e.ADAGIO.windows = e.ADAGIO.windows || [], + e.document.addEventListener("adagio.measure.afterStart", (function(e) { + a.store(e.detail.measure) + } + )); e.ADAGIO.queue.length; ) + Et(e.ADAGIO.queue.shift(), { + navigationFeatures: n, + adagioNavigation: t, + featuresManager: i, + measurersManager: a + }); + e.ADAGIO.queue.push = function(e) { + try { + Et(e, { + navigationFeatures: n, + adagioNavigation: t, + featuresManager: i, + measurersManager: a + }) + } catch (t) { + Ne("process queue", e.action, t) + } + } + }(), + Pe("adagio.js loaded: vers. ".concat("2.1.8")), + e.ADAGIO.loaded = !0) + }(); + var St = Ce()._ADAGIO && Ce()._ADAGIO.queueHistory ? Ce()._ADAGIO.queueHistory : []; + return e.queueHistory = St, + e +}({}); +try { + window.top.location.href ? top._ADAGIO = _ADAGIO : window._ADAGIO = _ADAGIO +} catch (e) {} diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index 0c044b0d63e..9f81ab8e778 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -160,6 +160,11 @@ function getCoppa() { function getUspConsent(bidderRequest) { return (deepAccess(bidderRequest, 'uspConsent')) ? { uspConsent: bidderRequest.uspConsent } : false; } + +function getSchain(bidRequest) { + return deepAccess(bidRequest, 'schain'); +} + // Enhanced ORTBConverter with additional data const converter = ortbConverter({ context: { @@ -214,6 +219,12 @@ const converter = ortbConverter({ // Set currency to USD deepSetValue(request, 'cur', ['USD']); + // Add schain if present + const schain = getSchain(bidderRequest.bids[0]); + if (schain) { + deepSetValue(request, 'source.ext.schain', schain); + } + return request; }, From 43f09a7a9577b97308e9cc53f99cd4b45a38410e Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:32:28 +0200 Subject: [PATCH 16/44] Added support for Eids --- modules/valuadBidAdapter.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index 9f81ab8e778..dd720a49a3e 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -165,6 +165,10 @@ function getSchain(bidRequest) { return deepAccess(bidRequest, 'schain'); } +function getEids(bidRequest) { + return deepAccess(bidRequest, 'userIdAsEids'); +} + // Enhanced ORTBConverter with additional data const converter = ortbConverter({ context: { @@ -225,6 +229,12 @@ const converter = ortbConverter({ deepSetValue(request, 'source.ext.schain', schain); } + // Add eids if present + const eids = getEids(bidderRequest.bids[0]); + if (eids) { + deepSetValue(request, 'user.ext.eids', eids); + } + return request; }, From d889fa84a3b07826370d075d39deaed23bf8240c Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:33:24 +0200 Subject: [PATCH 17/44] Added support for extended ortb2 data --- modules/valuadBidAdapter.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index dd720a49a3e..af87ae5491f 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -235,6 +235,14 @@ const converter = ortbConverter({ deepSetValue(request, 'user.ext.eids', eids); } + const ortb2 = bidderRequest.ortb2 || {}; + if (ortb2.site?.ext?.data) { + deepSetValue(request, 'site.ext.data', { + ...request.site.ext.data, + ...ortb2.site.ext.data + }); + } + return request; }, From b88806152894e639514c5ccf893502b6383862fa Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:37:14 +0200 Subject: [PATCH 18/44] Added support for video impressions --- modules/valuadBidAdapter.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index af87ae5491f..29c66d17a84 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -169,6 +169,28 @@ function getEids(bidRequest) { return deepAccess(bidRequest, 'userIdAsEids'); } +function processVideoParams(bid) { + const videoParams = deepAccess(bid, 'mediaTypes.video', {}); + const playerSize = videoParams.playerSize || []; + + return cleanObj({ + mimes: videoParams.mimes, + minduration: videoParams.minduration, + maxduration: videoParams.maxduration, + protocols: videoParams.protocols, + w: playerSize[0]?.[0], + h: playerSize[0]?.[1], + startdelay: videoParams.startdelay, + placement: videoParams.placement, + linearity: videoParams.linearity, + skip: videoParams.skip, + skipmin: videoParams.skipmin, + skipafter: videoParams.skipafter, + playbackmethod: videoParams.playbackmethod, + api: videoParams.api + }); +} + // Enhanced ORTBConverter with additional data const converter = ortbConverter({ context: { @@ -300,6 +322,13 @@ const converter = ortbConverter({ } } + if (bid.mediaTypes?.video) { + imp.video = { + ...imp.video, + ...processVideoParams(bid) + }; + } + return imp; }, From e53c27ac012c04f84f9f461b6a64cdcf5d1114d4 Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:38:46 +0200 Subject: [PATCH 19/44] Added support for native banners --- modules/valuadBidAdapter.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index 29c66d17a84..c609c0f5cbc 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -191,6 +191,36 @@ function processVideoParams(bid) { }); } +function processNativeAssets(nativeParams) { + const assets = []; + let id = 1; + + if (nativeParams.title) { + assets.push({ + id: id++, + required: nativeParams.title.required ? 1 : 0, + title: { + len: nativeParams.title.len || 140 + } + }); + } + + if (nativeParams.image) { + assets.push({ + id: id++, + required: nativeParams.image.required ? 1 : 0, + img: { + type: 3, // Main image + w: nativeParams.image.sizes[0], + h: nativeParams.image.sizes[1], + mimes: nativeParams.image.mimes || ['image/jpeg', 'image/png'] + } + }); + } + + return assets; +} + // Enhanced ORTBConverter with additional data const converter = ortbConverter({ context: { @@ -329,6 +359,13 @@ const converter = ortbConverter({ }; } + if (bid.mediaTypes?.native) { + imp.native = { + ver: '1.2', + assets: processNativeAssets(bid.mediaTypes.native) + }; + } + return imp; }, From d141a58f59a93c85064263e34ff53e6935aaeab1 Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:39:54 +0200 Subject: [PATCH 20/44] Added timeout data to request --- modules/valuadBidAdapter.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index c609c0f5cbc..7541b5d1d06 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -295,6 +295,11 @@ const converter = ortbConverter({ }); } + const tmax = bidderRequest.timeout; + if (tmax) { + deepSetValue(request, 'tmax', tmax); + } + return request; }, From bb263783f9e29b6a7ab5cf407d27e12e4f88b109 Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:54:03 +0200 Subject: [PATCH 21/44] Added RTD data --- modules/valuadBidAdapter.js | 453 +++++++++++++++++++++++++++++++++--- 1 file changed, 416 insertions(+), 37 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index 7541b5d1d06..391ad23a1eb 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -21,20 +21,213 @@ const BIDDER_CODE = 'valuad'; const AD_URL = 'https://valuad-server-test.appspot.com/adapter'; const WON_URL = 'https://hb-dot-valuad.appspot.com/adapter/win'; +const DEFAULT_RTD_CONFIG = { + auctionDelay: 50, // ms to wait for RTD data + params: { + handleRtd: true, + handleViewability: true, + handleUserData: true + } +}; + +const StorageManager = { + data: {}, + + init() { + const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); + w.VALUAD = w.VALUAD || {}; + this.data = w.VALUAD; + + // Load data from persistent storage + this.loadFromStorage(); + return this; + }, + + loadFromStorage() { + try { + // Load session data + const sessionData = sessionStorage.getItem('valuad_session'); + if (sessionData) { + this.data.session = JSON.parse(sessionData); + } + + // Load historical data + const historicalData = localStorage.getItem('valuad_historical'); + if (historicalData) { + this.data.historical = JSON.parse(historicalData); + } + + // Load RTD data + const rtdData = localStorage.getItem('valuad_rtd'); + if (rtdData) { + const parsedRtdData = JSON.parse(rtdData); + // Only load non-expired data + if (parsedRtdData.expiry && parsedRtdData.expiry > Date.now()) { + this.data.rtd = parsedRtdData.value; + } + } + } catch (e) { + logInfo('Valuad: Error loading from storage', e); + } + }, + + saveToStorage() { + try { + // Save session data + sessionStorage.setItem('valuad_session', JSON.stringify(this.data.session)); + + // Save historical data + localStorage.setItem('valuad_historical', JSON.stringify(this.data.historical)); + + // Save RTD data + if (this.data.rtd) { + localStorage.setItem('valuad_rtd', JSON.stringify({ + value: this.data.rtd, + expiry: Date.now() + (30 * 60 * 1000) // 30 minutes TTL + })); + } + } catch (e) { + logInfo('Valuad: Error saving to storage', e); + } + }, + + set(key, value, persistent = false) { + this.data[key] = value; + if (persistent) { + this.saveToStorage(); + } + return value; + }, + + get(key) { + return this.data[key]; + }, + + update(key, value, persistent = false) { + this.data[key] = { + ...this.data[key], + ...value, + lastUpdate: Date.now() + }; + if (persistent) { + this.saveToStorage(); + } + return this.data[key]; + }, + + updateHistoricalData(data) { + const now = new Date(); + const dateKey = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`; + + this.data.historical = this.data.historical || {}; + this.data.historical[dateKey] = this.data.historical[dateKey] || { + pageviews: 0, + adImpressions: {}, + bidRequests: 0, + bidResponses: 0, + revenue: 0 + }; + + this.data.historical[dateKey] = { + ...this.data.historical[dateKey], + ...data + }; + + // Save to localStorage + localStorage.setItem('valuad_historical', JSON.stringify(this.data.historical)); + }, + + getHistoricalData(days = 30) { + const historical = this.data.historical || {}; + const now = new Date(); + const result = {}; + + // Get last N days of data + for (let i = 0; i < days; i++) { + const date = new Date(now); + date.setDate(date.getDate() - i); + const dateKey = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; + if (historical[dateKey]) { + result[dateKey] = historical[dateKey]; + } + } + + return result; + }, + + updateSessionData(data) { + const session = this.data.session || { + id: generateUUID(), + startTime: Date.now(), + pageviews: 0, + adImpressions: {}, + bidRequests: 0, + bidResponses: 0, + revenue: 0 + }; + + this.data.session = { + ...session, + ...data, + lastUpdate: Date.now() + }; + + // Save to sessionStorage + sessionStorage.setItem('valuad_session', JSON.stringify(this.data.session)); + }, + + cleanup() { + try { + // Clean up expired RTD data + const rtdData = localStorage.getItem('valuad_rtd'); + if (rtdData) { + const parsed = JSON.parse(rtdData); + if (parsed.expiry && parsed.expiry < Date.now()) { + localStorage.removeItem('valuad_rtd'); + delete this.data.rtd; + } + } + + // Clean up historical data older than 90 days + const historical = this.data.historical || {}; + const cutoffDate = new Date(); + cutoffDate.setDate(cutoffDate.getDate() - 90); + + Object.keys(historical).forEach(dateKey => { + const [year, month, day] = dateKey.split('-').map(Number); + const dataDate = new Date(year, month - 1, day); + if (dataDate < cutoffDate) { + delete historical[dateKey]; + } + }); + + localStorage.setItem('valuad_historical', JSON.stringify(historical)); + } catch (e) { + logInfo('Valuad: Error in cleanup', e); + } + } +}; + export const _VALUAD = (function() { - const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); - - w.VALUAD = w.VALUAD || {}; - w.VALUAD.pageviewId = w.VALUAD.pageviewId || generateUUID(); - w.VALUAD.sessionId = w.VALUAD.sessionId || generateUUID(); - w.VALUAD.sessionStartTime = w.VALUAD.sessionStartTime || Date.now(); - w.VALUAD.pageLoadTime = w.VALUAD.pageLoadTime || window.performance?.timing?.domContentLoadedEventEnd - window.performance?.timing?.navigationStart; - w.VALUAD.userActivity = w.VALUAD.userActivity || { - lastActivityTime: Date.now(), - pageviewCount: (w.VALUAD.userActivity?.pageviewCount || 0) + 1 - }; + const storage = StorageManager.init(); + + // Update session data + storage.updateSessionData({ + pageviews: (storage.get('session')?.pageviews || 0) + 1 + }); - return w.VALUAD; + // Initialize RTD data + storage.set('rtdData', { + userActivity: { + lastActivityTime: Date.now(), + pageviewCount: (storage.get('session')?.pageviews || 0) + } + }, true); // true for persistent storage + + // Cleanup expired data periodically + setInterval(() => storage.cleanup(), 5 * 60 * 1000); + + return storage; })(); // Helper functions to enrich data @@ -232,6 +425,57 @@ const converter = ortbConverter({ const device = getDevice(); const site = getSite(bidderRequest); const session = getSession(); + const rtdConfig = getRtdConfig(); + + // Get session and historical data + const sessionData = _VALUAD.get('session') || {}; + const historicalData = _VALUAD.getHistoricalData(30); // Get last 30 days + + // Calculate aggregate metrics from historical data + const aggregateHistorical = Object.values(historicalData).reduce((acc, daily) => { + return { + totalRevenue: (acc.totalRevenue || 0) + (daily.revenue || 0), + totalImpressions: (acc.totalImpressions || 0) + Object.values(daily.adImpressions || {}).reduce((sum, count) => sum + count, 0), + totalBidRequests: (acc.totalBidRequests || 0) + (daily.bidRequests || 0), + totalBidResponses: (acc.totalBidResponses || 0) + (daily.bidResponses || 0), + }; + }, {}); + + // Add enriched data to the request + deepSetValue(request, 'site.ext.data.valuad_analytics', { + session: { + id: sessionData.id, + startTime: sessionData.startTime, + pageviews: sessionData.pageviews, + duration: Date.now() - sessionData.startTime, + revenue: sessionData.revenue || 0, + bidRequests: sessionData.bidRequests || 0, + bidResponses: sessionData.bidResponses || 0, + adImpressions: sessionData.adImpressions || {}, + lastUpdate: sessionData.lastUpdate + }, + historical: { + last30Days: { + ...aggregateHistorical, + averageDailyRevenue: aggregateHistorical.totalRevenue / Object.keys(historicalData).length, + averageDailyImpressions: aggregateHistorical.totalImpressions / Object.keys(historicalData).length, + bidResponseRate: aggregateHistorical.totalBidResponses / (aggregateHistorical.totalBidRequests || 1), + }, + // Include today's data separately for immediate context + today: historicalData[Object.keys(historicalData)[0]] || {}, + } + }); + + // Add performance metrics for the current page + deepSetValue(request, 'site.ext.data.valuad_rtd', { + ...request.site.ext.data.valuad_rtd, + performance: { + pageLoadTime: _VALUAD.get('pageLoadTime'), + timeOnPage: Date.now() - sessionData.startTime, + userInteractions: _VALUAD.get('userActivity')?.interactions || 0, + maxScroll: _VALUAD.get('userActivity')?.maxScroll || 0, + } + }); const gdprConsent = getGdprConsent(bidderRequest).consentRequired || 0; const gdprConsentString = getGdprConsent(bidderRequest).consentString || ''; @@ -256,16 +500,18 @@ const converter = ortbConverter({ } }); - deepSetValue(request, 'site.ext.data.valuad_rtd', { - pageviewId: _VALUAD.pageviewId, - session: session, - features: { - page_dimensions: `${document.documentElement.scrollWidth}x${document.documentElement.scrollHeight}`, - viewport_dimensions: `${window.innerWidth}x${window.innerHeight}`, - user_timestamp: Math.floor(Date.now() / 1000), - dom_loading: window.performance?.timing?.domContentLoadedEventEnd - window.performance?.timing?.navigationStart - } - }); + if (rtdConfig.params.handleRtd) { + const rtdData = collectRtdData(); + + deepSetValue(request, 'site.ext.data.valuad_rtd', { + ...request.site.ext.data.valuad_rtd, + ...rtdData, + config: { + enabled: true, + auctionDelay: rtdConfig.auctionDelay + } + }); + } // Add bid parameters if (bidderRequest && bidderRequest.bids && bidderRequest.bids.length) { @@ -406,12 +652,20 @@ const isBidRequestValid = () => (bid = {}) => { }; const buildRequests = (adUrl) => (validBidRequests = [], bidderRequest = {}) => { - // Add bid-level metadata for our server to use validBidRequests = validBidRequests.map(req => { req.valuadMeta = { pageviewId: _VALUAD.pageviewId, - adUnitPosition: detectAdUnitPosition(req.adUnitCode) + adUnitPosition: detectAdUnitPosition(req.adUnitCode), + rtd: _VALUAD.rtdData, + viewability: _VALUAD.viewabilityData?.[req.adUnitCode], + userActivity: _VALUAD.userActivity }; + + // Start viewability tracking + if (getRtdConfig().params.handleViewability) { + trackViewability(req.adUnitCode); + } + return req; }); @@ -430,10 +684,21 @@ const buildRequests = (adUrl) => (validBidRequests = [], bidderRequest = {}) => const interpretResponse = () => (response, request) => { const bidResponses = converter.fromORTB({response: response.body, request: request.data}).bids; - // Process server-side data - if (response.body && response.body.ext && response.body.ext.valuad) { - // Store any server-side enhanced data for future use - _VALUAD.serverData = response.body.ext.valuad; + if (response.body?.ext?.valuad_rtd) { + const rtdData = response.body.ext.valuad_rtd; + _VALUAD.setWithExpiry('serverRtdData', rtdData, 5 * 60 * 1000); // 5 minutes TTL + + return bidResponses.map(bid => ({ + ...bid, + meta: { + ...bid.meta, + rtd: { + segments: rtdData.segments, + viewability: _VALUAD.getWithExpiry('viewabilityData')?.[bid.adUnitCode], + performance: rtdData.performance + } + } + })); } return bidResponses; @@ -453,15 +718,36 @@ const getUserSyncs = () => (syncOptions, serverResponses) => { }; const onBidWon = (bid) => { - const { - adUnitCode, adUnitId, auctionId, bidder, cpm, currency, originalCpm, originalCurrency, size, vbid, vid, - } = bid; - const bidStr = JSON.stringify({ - adUnitCode, adUnitId, auctionId, bidder, cpm, currency, originalCpm, originalCurrency, size, vbid, vid, - }); - const encodedBidStr = window.btoa(bidStr); - triggerPixel(WON_URL + '?b=' + encodedBidStr); -} + try { + const { + adUnitCode, adUnitId, auctionId, bidder, cpm, currency, originalCpm, originalCurrency, size, vbid, vid, + } = bid; + + // Update historical and session data + _VALUAD.updateHistoricalData({ + revenue: (_VALUAD.get('historical')?.revenue || 0) + cpm, + adImpressions: { + [adUnitCode]: (_VALUAD.get('historical')?.adImpressions?.[adUnitCode] || 0) + 1 + } + }); + + _VALUAD.updateSessionData({ + revenue: (_VALUAD.get('session')?.revenue || 0) + cpm, + adImpressions: { + [adUnitCode]: (_VALUAD.get('session')?.adImpressions?.[adUnitCode] || 0) + 1 + } + }); + + // Send win notification + const bidStr = JSON.stringify({ + adUnitCode, adUnitId, auctionId, bidder, cpm, currency, originalCpm, originalCurrency, size, vbid, vid, + }); + const encodedBidStr = window.btoa(bidStr); + triggerPixel(WON_URL + '?b=' + encodedBidStr); + } catch (e) { + logInfo('Valuad: Error in onBidWon', e); + } +}; export const spec = { code: BIDDER_CODE, @@ -475,3 +761,96 @@ export const spec = { }; registerBidder(spec); + +// Add RTD configuration getter +function getRtdConfig() { + return config.getConfig('valuad.rtd') || DEFAULT_RTD_CONFIG; +} + +// Add this function to collect more RTD data +function collectRtdData() { + return { + page: { + dimensions: `${document.documentElement.scrollWidth}x${document.documentElement.scrollHeight}`, + viewport: `${window.innerWidth}x${window.innerHeight}`, + url: window.location.href, + title: document.title, + keywords: Array.from(document.getElementsByTagName('meta')) + .filter(meta => meta.name === 'keywords') + .map(meta => meta.content) + .join(','), + loadTime: window.performance?.timing?.domContentLoadedEventEnd - window.performance?.timing?.navigationStart, + referrer: document.referrer + }, + user: { + timestamp: Math.floor(Date.now() / 1000), + language: navigator.language, + userAgent: navigator.userAgent + }, + session: { + id: _VALUAD.sessionId, + pageviews: _VALUAD.userActivity.pageviewCount, + duration: Date.now() - _VALUAD.sessionStartTime, + lastActivity: _VALUAD.userActivity.lastActivityTime + }, + performance: { + navigationStart: window.performance?.timing?.navigationStart, + domInteractive: window.performance?.timing?.domInteractive, + domComplete: window.performance?.timing?.domComplete, + loadEventEnd: window.performance?.timing?.loadEventEnd + } + }; +} + +// Add viewability tracking function +function trackViewability(adUnitCode) { + if ('IntersectionObserver' in window) { + const element = document.getElementById(adUnitCode); + if (!element) return null; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + const viewabilityData = { + inView: entry.isIntersecting, + visibleRatio: entry.intersectionRatio, + time: Date.now(), + adUnitCode: adUnitCode + }; + + _VALUAD.update('viewabilityData', { + [adUnitCode]: viewabilityData + }); + }); + }, { + threshold: [0, 0.25, 0.5, 0.75, 1] + }); + + observer.observe(element); + return observer; + } + return null; +} + +// Add event tracking +function initRtdEventListeners() { + // Track scroll depth + let maxScroll = 0; + window.addEventListener('scroll', () => { + const scrollPercent = (window.scrollY + window.innerHeight) / document.documentElement.scrollHeight; + maxScroll = Math.max(maxScroll, scrollPercent); + _VALUAD.userActivity.maxScroll = maxScroll; + }); + + // Track time on page + const startTime = Date.now(); + window.addEventListener('beforeunload', () => { + const timeOnPage = Date.now() - startTime; + _VALUAD.userActivity.timeOnPage = timeOnPage; + }); + + // Track user interactions + document.addEventListener('click', () => { + _VALUAD.userActivity.lastActivityTime = Date.now(); + _VALUAD.userActivity.interactions = (_VALUAD.userActivity.interactions || 0) + 1; + }); +} From 2f5813be9fe795629fcb4306575be75548df47b3 Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:54:30 +0200 Subject: [PATCH 22/44] Improved storage handeling --- modules/valuadBidAdapter.js | 42 ++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index 391ad23a1eb..27fc1d02a58 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -71,20 +71,38 @@ const StorageManager = { } }, + isStorageAvailable(type) { + try { + const storage = window[type]; + const x = '__storage_test__'; + storage.setItem(x, x); + storage.removeItem(x); + return true; + } catch (e) { + return false; + } + }, + saveToStorage() { try { - // Save session data - sessionStorage.setItem('valuad_session', JSON.stringify(this.data.session)); - - // Save historical data - localStorage.setItem('valuad_historical', JSON.stringify(this.data.historical)); - - // Save RTD data - if (this.data.rtd) { - localStorage.setItem('valuad_rtd', JSON.stringify({ - value: this.data.rtd, - expiry: Date.now() + (30 * 60 * 1000) // 30 minutes TTL - })); + if (this.isStorageAvailable('localStorage')) { + // Save session data + localStorage.setItem('valuad_session', JSON.stringify(this.data.session)); + + // Save historical data + localStorage.setItem('valuad_historical', JSON.stringify(this.data.historical)); + + // Save RTD data + if (this.data.rtd) { + localStorage.setItem('valuad_rtd', JSON.stringify({ + value: this.data.rtd, + expiry: Date.now() + (30 * 60 * 1000) // 30 minutes TTL + })); + } + } + if (this.isStorageAvailable('sessionStorage')) { + // Save session data + sessionStorage.setItem('valuad_session', JSON.stringify(this.data.session)); } } catch (e) { logInfo('Valuad: Error saving to storage', e); From 7d1e8a0738f687f80568a1cd93062f22e73df74c Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:54:52 +0200 Subject: [PATCH 23/44] Added memory management for observers --- modules/valuadBidAdapter.js | 490 +++++------------------------------- 1 file changed, 59 insertions(+), 431 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index 27fc1d02a58..ae4b5a1dcef 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -21,231 +21,20 @@ const BIDDER_CODE = 'valuad'; const AD_URL = 'https://valuad-server-test.appspot.com/adapter'; const WON_URL = 'https://hb-dot-valuad.appspot.com/adapter/win'; -const DEFAULT_RTD_CONFIG = { - auctionDelay: 50, // ms to wait for RTD data - params: { - handleRtd: true, - handleViewability: true, - handleUserData: true - } -}; - -const StorageManager = { - data: {}, - - init() { - const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); - w.VALUAD = w.VALUAD || {}; - this.data = w.VALUAD; - - // Load data from persistent storage - this.loadFromStorage(); - return this; - }, - - loadFromStorage() { - try { - // Load session data - const sessionData = sessionStorage.getItem('valuad_session'); - if (sessionData) { - this.data.session = JSON.parse(sessionData); - } - - // Load historical data - const historicalData = localStorage.getItem('valuad_historical'); - if (historicalData) { - this.data.historical = JSON.parse(historicalData); - } - - // Load RTD data - const rtdData = localStorage.getItem('valuad_rtd'); - if (rtdData) { - const parsedRtdData = JSON.parse(rtdData); - // Only load non-expired data - if (parsedRtdData.expiry && parsedRtdData.expiry > Date.now()) { - this.data.rtd = parsedRtdData.value; - } - } - } catch (e) { - logInfo('Valuad: Error loading from storage', e); - } - }, - - isStorageAvailable(type) { - try { - const storage = window[type]; - const x = '__storage_test__'; - storage.setItem(x, x); - storage.removeItem(x); - return true; - } catch (e) { - return false; - } - }, - - saveToStorage() { - try { - if (this.isStorageAvailable('localStorage')) { - // Save session data - localStorage.setItem('valuad_session', JSON.stringify(this.data.session)); - - // Save historical data - localStorage.setItem('valuad_historical', JSON.stringify(this.data.historical)); - - // Save RTD data - if (this.data.rtd) { - localStorage.setItem('valuad_rtd', JSON.stringify({ - value: this.data.rtd, - expiry: Date.now() + (30 * 60 * 1000) // 30 minutes TTL - })); - } - } - if (this.isStorageAvailable('sessionStorage')) { - // Save session data - sessionStorage.setItem('valuad_session', JSON.stringify(this.data.session)); - } - } catch (e) { - logInfo('Valuad: Error saving to storage', e); - } - }, - - set(key, value, persistent = false) { - this.data[key] = value; - if (persistent) { - this.saveToStorage(); - } - return value; - }, - - get(key) { - return this.data[key]; - }, - - update(key, value, persistent = false) { - this.data[key] = { - ...this.data[key], - ...value, - lastUpdate: Date.now() - }; - if (persistent) { - this.saveToStorage(); - } - return this.data[key]; - }, - - updateHistoricalData(data) { - const now = new Date(); - const dateKey = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`; - - this.data.historical = this.data.historical || {}; - this.data.historical[dateKey] = this.data.historical[dateKey] || { - pageviews: 0, - adImpressions: {}, - bidRequests: 0, - bidResponses: 0, - revenue: 0 - }; - - this.data.historical[dateKey] = { - ...this.data.historical[dateKey], - ...data - }; - - // Save to localStorage - localStorage.setItem('valuad_historical', JSON.stringify(this.data.historical)); - }, - - getHistoricalData(days = 30) { - const historical = this.data.historical || {}; - const now = new Date(); - const result = {}; - - // Get last N days of data - for (let i = 0; i < days; i++) { - const date = new Date(now); - date.setDate(date.getDate() - i); - const dateKey = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; - if (historical[dateKey]) { - result[dateKey] = historical[dateKey]; - } - } - - return result; - }, - - updateSessionData(data) { - const session = this.data.session || { - id: generateUUID(), - startTime: Date.now(), - pageviews: 0, - adImpressions: {}, - bidRequests: 0, - bidResponses: 0, - revenue: 0 - }; - - this.data.session = { - ...session, - ...data, - lastUpdate: Date.now() - }; - - // Save to sessionStorage - sessionStorage.setItem('valuad_session', JSON.stringify(this.data.session)); - }, - - cleanup() { - try { - // Clean up expired RTD data - const rtdData = localStorage.getItem('valuad_rtd'); - if (rtdData) { - const parsed = JSON.parse(rtdData); - if (parsed.expiry && parsed.expiry < Date.now()) { - localStorage.removeItem('valuad_rtd'); - delete this.data.rtd; - } - } - - // Clean up historical data older than 90 days - const historical = this.data.historical || {}; - const cutoffDate = new Date(); - cutoffDate.setDate(cutoffDate.getDate() - 90); - - Object.keys(historical).forEach(dateKey => { - const [year, month, day] = dateKey.split('-').map(Number); - const dataDate = new Date(year, month - 1, day); - if (dataDate < cutoffDate) { - delete historical[dateKey]; - } - }); - - localStorage.setItem('valuad_historical', JSON.stringify(historical)); - } catch (e) { - logInfo('Valuad: Error in cleanup', e); - } - } -}; - export const _VALUAD = (function() { - const storage = StorageManager.init(); - - // Update session data - storage.updateSessionData({ - pageviews: (storage.get('session')?.pageviews || 0) + 1 - }); - - // Initialize RTD data - storage.set('rtdData', { - userActivity: { - lastActivityTime: Date.now(), - pageviewCount: (storage.get('session')?.pageviews || 0) - } - }, true); // true for persistent storage - - // Cleanup expired data periodically - setInterval(() => storage.cleanup(), 5 * 60 * 1000); + const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); + + w.VALUAD = w.VALUAD || {}; + w.VALUAD.pageviewId = w.VALUAD.pageviewId || generateUUID(); + w.VALUAD.sessionId = w.VALUAD.sessionId || generateUUID(); + w.VALUAD.sessionStartTime = w.VALUAD.sessionStartTime || Date.now(); + w.VALUAD.pageLoadTime = w.VALUAD.pageLoadTime || window.performance?.timing?.domContentLoadedEventEnd - window.performance?.timing?.navigationStart; + w.VALUAD.userActivity = w.VALUAD.userActivity || { + lastActivityTime: Date.now(), + pageviewCount: (w.VALUAD.userActivity?.pageviewCount || 0) + 1 + }; - return storage; + return w.VALUAD; })(); // Helper functions to enrich data @@ -443,57 +232,6 @@ const converter = ortbConverter({ const device = getDevice(); const site = getSite(bidderRequest); const session = getSession(); - const rtdConfig = getRtdConfig(); - - // Get session and historical data - const sessionData = _VALUAD.get('session') || {}; - const historicalData = _VALUAD.getHistoricalData(30); // Get last 30 days - - // Calculate aggregate metrics from historical data - const aggregateHistorical = Object.values(historicalData).reduce((acc, daily) => { - return { - totalRevenue: (acc.totalRevenue || 0) + (daily.revenue || 0), - totalImpressions: (acc.totalImpressions || 0) + Object.values(daily.adImpressions || {}).reduce((sum, count) => sum + count, 0), - totalBidRequests: (acc.totalBidRequests || 0) + (daily.bidRequests || 0), - totalBidResponses: (acc.totalBidResponses || 0) + (daily.bidResponses || 0), - }; - }, {}); - - // Add enriched data to the request - deepSetValue(request, 'site.ext.data.valuad_analytics', { - session: { - id: sessionData.id, - startTime: sessionData.startTime, - pageviews: sessionData.pageviews, - duration: Date.now() - sessionData.startTime, - revenue: sessionData.revenue || 0, - bidRequests: sessionData.bidRequests || 0, - bidResponses: sessionData.bidResponses || 0, - adImpressions: sessionData.adImpressions || {}, - lastUpdate: sessionData.lastUpdate - }, - historical: { - last30Days: { - ...aggregateHistorical, - averageDailyRevenue: aggregateHistorical.totalRevenue / Object.keys(historicalData).length, - averageDailyImpressions: aggregateHistorical.totalImpressions / Object.keys(historicalData).length, - bidResponseRate: aggregateHistorical.totalBidResponses / (aggregateHistorical.totalBidRequests || 1), - }, - // Include today's data separately for immediate context - today: historicalData[Object.keys(historicalData)[0]] || {}, - } - }); - - // Add performance metrics for the current page - deepSetValue(request, 'site.ext.data.valuad_rtd', { - ...request.site.ext.data.valuad_rtd, - performance: { - pageLoadTime: _VALUAD.get('pageLoadTime'), - timeOnPage: Date.now() - sessionData.startTime, - userInteractions: _VALUAD.get('userActivity')?.interactions || 0, - maxScroll: _VALUAD.get('userActivity')?.maxScroll || 0, - } - }); const gdprConsent = getGdprConsent(bidderRequest).consentRequired || 0; const gdprConsentString = getGdprConsent(bidderRequest).consentString || ''; @@ -518,18 +256,16 @@ const converter = ortbConverter({ } }); - if (rtdConfig.params.handleRtd) { - const rtdData = collectRtdData(); - - deepSetValue(request, 'site.ext.data.valuad_rtd', { - ...request.site.ext.data.valuad_rtd, - ...rtdData, - config: { - enabled: true, - auctionDelay: rtdConfig.auctionDelay - } - }); - } + deepSetValue(request, 'site.ext.data.valuad_rtd', { + pageviewId: _VALUAD.pageviewId, + session: session, + features: { + page_dimensions: `${document.documentElement.scrollWidth}x${document.documentElement.scrollHeight}`, + viewport_dimensions: `${window.innerWidth}x${window.innerHeight}`, + user_timestamp: Math.floor(Date.now() / 1000), + dom_loading: window.performance?.timing?.domContentLoadedEventEnd - window.performance?.timing?.navigationStart + } + }); // Add bid parameters if (bidderRequest && bidderRequest.bids && bidderRequest.bids.length) { @@ -670,20 +406,12 @@ const isBidRequestValid = () => (bid = {}) => { }; const buildRequests = (adUrl) => (validBidRequests = [], bidderRequest = {}) => { + // Add bid-level metadata for our server to use validBidRequests = validBidRequests.map(req => { req.valuadMeta = { pageviewId: _VALUAD.pageviewId, - adUnitPosition: detectAdUnitPosition(req.adUnitCode), - rtd: _VALUAD.rtdData, - viewability: _VALUAD.viewabilityData?.[req.adUnitCode], - userActivity: _VALUAD.userActivity + adUnitPosition: detectAdUnitPosition(req.adUnitCode) }; - - // Start viewability tracking - if (getRtdConfig().params.handleViewability) { - trackViewability(req.adUnitCode); - } - return req; }); @@ -702,21 +430,10 @@ const buildRequests = (adUrl) => (validBidRequests = [], bidderRequest = {}) => const interpretResponse = () => (response, request) => { const bidResponses = converter.fromORTB({response: response.body, request: request.data}).bids; - if (response.body?.ext?.valuad_rtd) { - const rtdData = response.body.ext.valuad_rtd; - _VALUAD.setWithExpiry('serverRtdData', rtdData, 5 * 60 * 1000); // 5 minutes TTL - - return bidResponses.map(bid => ({ - ...bid, - meta: { - ...bid.meta, - rtd: { - segments: rtdData.segments, - viewability: _VALUAD.getWithExpiry('viewabilityData')?.[bid.adUnitCode], - performance: rtdData.performance - } - } - })); + // Process server-side data + if (response.body && response.body.ext && response.body.ext.valuad) { + // Store any server-side enhanced data for future use + _VALUAD.serverData = response.body.ext.valuad; } return bidResponses; @@ -736,36 +453,40 @@ const getUserSyncs = () => (syncOptions, serverResponses) => { }; const onBidWon = (bid) => { - try { - const { - adUnitCode, adUnitId, auctionId, bidder, cpm, currency, originalCpm, originalCurrency, size, vbid, vid, - } = bid; - - // Update historical and session data - _VALUAD.updateHistoricalData({ - revenue: (_VALUAD.get('historical')?.revenue || 0) + cpm, - adImpressions: { - [adUnitCode]: (_VALUAD.get('historical')?.adImpressions?.[adUnitCode] || 0) + 1 - } - }); + const { + adUnitCode, adUnitId, auctionId, bidder, cpm, currency, originalCpm, originalCurrency, size, vbid, vid, + } = bid; + const bidStr = JSON.stringify({ + adUnitCode, adUnitId, auctionId, bidder, cpm, currency, originalCpm, originalCurrency, size, vbid, vid, + }); + const encodedBidStr = window.btoa(bidStr); + triggerPixel(WON_URL + '?b=' + encodedBidStr); +} - _VALUAD.updateSessionData({ - revenue: (_VALUAD.get('session')?.revenue || 0) + cpm, - adImpressions: { - [adUnitCode]: (_VALUAD.get('session')?.adImpressions?.[adUnitCode] || 0) + 1 - } - }); +// Add observer cleanup +const observers = new Map(); - // Send win notification - const bidStr = JSON.stringify({ - adUnitCode, adUnitId, auctionId, bidder, cpm, currency, originalCpm, originalCurrency, size, vbid, vid, - }); - const encodedBidStr = window.btoa(bidStr); - triggerPixel(WON_URL + '?b=' + encodedBidStr); - } catch (e) { - logInfo('Valuad: Error in onBidWon', e); +function trackViewability(adUnitCode) { + // Cleanup existing observer if any + if (observers.has(adUnitCode)) { + observers.get(adUnitCode).disconnect(); } -}; + + const observer = new IntersectionObserver((entries) => { + // ... existing code + }); + + observers.set(adUnitCode, observer); + return observer; +} + +// Add cleanup function +function cleanupViewabilityTracking(adUnitCode) { + if (observers.has(adUnitCode)) { + observers.get(adUnitCode).disconnect(); + observers.delete(adUnitCode); + } +} export const spec = { code: BIDDER_CODE, @@ -779,96 +500,3 @@ export const spec = { }; registerBidder(spec); - -// Add RTD configuration getter -function getRtdConfig() { - return config.getConfig('valuad.rtd') || DEFAULT_RTD_CONFIG; -} - -// Add this function to collect more RTD data -function collectRtdData() { - return { - page: { - dimensions: `${document.documentElement.scrollWidth}x${document.documentElement.scrollHeight}`, - viewport: `${window.innerWidth}x${window.innerHeight}`, - url: window.location.href, - title: document.title, - keywords: Array.from(document.getElementsByTagName('meta')) - .filter(meta => meta.name === 'keywords') - .map(meta => meta.content) - .join(','), - loadTime: window.performance?.timing?.domContentLoadedEventEnd - window.performance?.timing?.navigationStart, - referrer: document.referrer - }, - user: { - timestamp: Math.floor(Date.now() / 1000), - language: navigator.language, - userAgent: navigator.userAgent - }, - session: { - id: _VALUAD.sessionId, - pageviews: _VALUAD.userActivity.pageviewCount, - duration: Date.now() - _VALUAD.sessionStartTime, - lastActivity: _VALUAD.userActivity.lastActivityTime - }, - performance: { - navigationStart: window.performance?.timing?.navigationStart, - domInteractive: window.performance?.timing?.domInteractive, - domComplete: window.performance?.timing?.domComplete, - loadEventEnd: window.performance?.timing?.loadEventEnd - } - }; -} - -// Add viewability tracking function -function trackViewability(adUnitCode) { - if ('IntersectionObserver' in window) { - const element = document.getElementById(adUnitCode); - if (!element) return null; - - const observer = new IntersectionObserver((entries) => { - entries.forEach(entry => { - const viewabilityData = { - inView: entry.isIntersecting, - visibleRatio: entry.intersectionRatio, - time: Date.now(), - adUnitCode: adUnitCode - }; - - _VALUAD.update('viewabilityData', { - [adUnitCode]: viewabilityData - }); - }); - }, { - threshold: [0, 0.25, 0.5, 0.75, 1] - }); - - observer.observe(element); - return observer; - } - return null; -} - -// Add event tracking -function initRtdEventListeners() { - // Track scroll depth - let maxScroll = 0; - window.addEventListener('scroll', () => { - const scrollPercent = (window.scrollY + window.innerHeight) / document.documentElement.scrollHeight; - maxScroll = Math.max(maxScroll, scrollPercent); - _VALUAD.userActivity.maxScroll = maxScroll; - }); - - // Track time on page - const startTime = Date.now(); - window.addEventListener('beforeunload', () => { - const timeOnPage = Date.now() - startTime; - _VALUAD.userActivity.timeOnPage = timeOnPage; - }); - - // Track user interactions - document.addEventListener('click', () => { - _VALUAD.userActivity.lastActivityTime = Date.now(); - _VALUAD.userActivity.interactions = (_VALUAD.userActivity.interactions || 0) + 1; - }); -} From 9781b1dbae031bb36a26617117470a1c07fe2660 Mon Sep 17 00:00:00 2001 From: natanavra Date: Thu, 10 Apr 2025 11:22:04 +0300 Subject: [PATCH 24/44] Update code to most recent working copy live in maariv --- modules/valuadBidAdapter.js | 30 +++--------------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index ae4b5a1dcef..90547512b43 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -16,9 +16,10 @@ import { import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; import { config } from '../src/config.js'; import { parseDomain } from '../src/refererDetection.js'; +import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; const BIDDER_CODE = 'valuad'; -const AD_URL = 'https://valuad-server-test.appspot.com/adapter'; +const AD_URL = 'https://rtb.valuad.io/adapter'; const WON_URL = 'https://hb-dot-valuad.appspot.com/adapter/win'; export const _VALUAD = (function() { @@ -97,7 +98,7 @@ function detectAdUnitPosition(adUnitCode) { const element = document.getElementById(adUnitCode) || document.getElementById(getGptSlotInfoForAdUnitCode(adUnitCode)?.divId); if (!element) return null; - const rect = element.getBoundingClientRect(); + const rect = getBoundingClientRect(element); const docElement = document.documentElement; const pageWidth = docElement.clientWidth; const pageHeight = docElement.scrollHeight; @@ -463,31 +464,6 @@ const onBidWon = (bid) => { triggerPixel(WON_URL + '?b=' + encodedBidStr); } -// Add observer cleanup -const observers = new Map(); - -function trackViewability(adUnitCode) { - // Cleanup existing observer if any - if (observers.has(adUnitCode)) { - observers.get(adUnitCode).disconnect(); - } - - const observer = new IntersectionObserver((entries) => { - // ... existing code - }); - - observers.set(adUnitCode, observer); - return observer; -} - -// Add cleanup function -function cleanupViewabilityTracking(adUnitCode) { - if (observers.has(adUnitCode)) { - observers.get(adUnitCode).disconnect(); - observers.delete(adUnitCode); - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], From 027b65696249e3cb7d8b1ba1ce57820b77a5121d Mon Sep 17 00:00:00 2001 From: natanavra Date: Thu, 17 Apr 2025 20:20:14 +0300 Subject: [PATCH 25/44] Organize functions for simplicity --- modules/valuadBidAdapter.js | 67 ++++++++++++------------------------- 1 file changed, 22 insertions(+), 45 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index 90547512b43..2e2dbc5ba31 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -152,24 +152,6 @@ function getGdprConsent(bidderRequest) { }); } -function getCoppa() { - return { - required: config.getConfig('coppa') === true ? 1 : 0 - }; -} - -function getUspConsent(bidderRequest) { - return (deepAccess(bidderRequest, 'uspConsent')) ? { uspConsent: bidderRequest.uspConsent } : false; -} - -function getSchain(bidRequest) { - return deepAccess(bidRequest, 'schain'); -} - -function getEids(bidRequest) { - return deepAccess(bidRequest, 'userIdAsEids'); -} - function processVideoParams(bid) { const videoParams = deepAccess(bid, 'mediaTypes.video', {}); const playerSize = videoParams.playerSize || []; @@ -234,10 +216,9 @@ const converter = ortbConverter({ const site = getSite(bidderRequest); const session = getSession(); - const gdprConsent = getGdprConsent(bidderRequest).consentRequired || 0; - const gdprConsentString = getGdprConsent(bidderRequest).consentString || ''; - const uspConsent = getUspConsent(bidderRequest).uspConsent || ''; - const coppa = getCoppa().required; + const gdpr = getGdprConsent(bidderRequest); + const uspConsent = deepAccess(bidderRequest, 'uspConsent') || ''; + const coppa = config.getConfig('coppa') === true ? 1 : 0; const { gpp, gpp_sid: gppSid } = deepAccess(bidderRequest, 'ortb2.regs', {}); const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); @@ -246,11 +227,11 @@ const converter = ortbConverter({ deepSetValue(request, 'site', {...request.site, ...site}); deepSetValue(request, 'regs', { - gdpr: gdprConsent, + gdpr: gdpr.consentRequired || 0, coppa: coppa, us_privacy: uspConsent, ext: { - gdpr_conset: gdprConsentString, + gdpr_conset: gdpr.consentString || '', gpp: gpp || '', gppSid: gppSid || [], dsa: dsa, @@ -277,13 +258,13 @@ const converter = ortbConverter({ deepSetValue(request, 'cur', ['USD']); // Add schain if present - const schain = getSchain(bidderRequest.bids[0]); + const schain = deepAccess(bidderRequest.bids[0], 'schain'); if (schain) { deepSetValue(request, 'source.ext.schain', schain); } // Add eids if present - const eids = getEids(bidderRequest.bids[0]); + const eids = deepAccess(bidderRequest.bids[0], 'userIdAsEids'); if (eids) { deepSetValue(request, 'user.ext.eids', eids); } @@ -387,7 +368,7 @@ const converter = ortbConverter({ }, }); -const isBidRequestValid = () => (bid = {}) => { +function isBidRequestValid(bid = {}) { const { params, bidId, mediaTypes } = bid; const foundKeys = bid && bid.params && bid.params.placementId; @@ -404,9 +385,9 @@ const isBidRequestValid = () => (bid = {}) => { } return valid; -}; +} -const buildRequests = (adUrl) => (validBidRequests = [], bidderRequest = {}) => { +function buildRequests(validBidRequests = [], bidderRequest = {}) { // Add bid-level metadata for our server to use validBidRequests = validBidRequests.map(req => { req.valuadMeta = { @@ -423,12 +404,12 @@ const buildRequests = (adUrl) => (validBidRequests = [], bidderRequest = {}) => return [{ method: 'POST', - url: adUrl, + url: AD_URL, data }]; -}; +} -const interpretResponse = () => (response, request) => { +function interpretResponse(response, request) { const bidResponses = converter.fromORTB({response: response.body, request: request.data}).bids; // Process server-side data @@ -438,22 +419,20 @@ const interpretResponse = () => (response, request) => { } return bidResponses; -}; +} -const getUserSyncs = () => (syncOptions, serverResponses) => { +function getUserSyncs(syncOptions, serverResponses) { if (!serverResponses.length || serverResponses[0].body === '' || !serverResponses[0].body.userSyncs) { return false; } - const syncs = serverResponses[0].body.userSyncs.map(sync => ({ + return serverResponses[0].body.userSyncs.map(sync => ({ type: sync.type === 'iframe' ? 'iframe' : 'image', url: sync.url })); +} - return syncs; -}; - -const onBidWon = (bid) => { +function onBidWon(bid) { const { adUnitCode, adUnitId, auctionId, bidder, cpm, currency, originalCpm, originalCurrency, size, vbid, vid, } = bid; @@ -467,12 +446,10 @@ const onBidWon = (bid) => { export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - - isBidRequestValid: isBidRequestValid(), - buildRequests: buildRequests(AD_URL), - interpretResponse: interpretResponse(), - getUserSyncs: getUserSyncs(), + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, onBidWon, }; - registerBidder(spec); From 1716b6e859eb616f29552717edd49c573c59e3aa Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Mon, 21 Apr 2025 15:29:08 +0300 Subject: [PATCH 26/44] Added unit testing for the adapter + added try-catch on ortbConvertor for bid responses. --- modules/valuadBidAdapter.js | 25 +- test/spec/modules/valuadBidAdapter_spec.js | 602 +++++++++++++++++++++ 2 files changed, 619 insertions(+), 8 deletions(-) create mode 100644 test/spec/modules/valuadBidAdapter_spec.js diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index 2e2dbc5ba31..95da63b4b69 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -357,12 +357,21 @@ const converter = ortbConverter({ }, bidResponse(buildBidResponse, bid, context) { - const bidResponse = buildBidResponse(bid, context); - if (bid.vbid) { - bidResponse.vbid = bid.vbid; - } - if (context.bidRequest?.params?.placementId) { - bidResponse.vid = context.bidRequest.params.placementId; + let bidResponse; + try { + bidResponse = buildBidResponse(bid, context); + + if (bidResponse) { + if (bid.vbid) { + bidResponse.vbid = bid.vbid; + } + if (context.bidRequest?.params?.placementId) { + bidResponse.vid = context.bidRequest.params.placementId; + } + } + } catch (e) { + logInfo('[VALUAD CONVERTER] Error calling buildBidResponse:', e, 'Bid:', bid); + return; } return bidResponse; }, @@ -410,11 +419,11 @@ function buildRequests(validBidRequests = [], bidderRequest = {}) { } function interpretResponse(response, request) { + // Restore original call, remove logging and safe navigation const bidResponses = converter.fromORTB({response: response.body, request: request.data}).bids; - // Process server-side data + // Restore original server-side data processing if (response.body && response.body.ext && response.body.ext.valuad) { - // Store any server-side enhanced data for future use _VALUAD.serverData = response.body.ext.valuad; } diff --git a/test/spec/modules/valuadBidAdapter_spec.js b/test/spec/modules/valuadBidAdapter_spec.js new file mode 100644 index 00000000000..8c4bcd791b8 --- /dev/null +++ b/test/spec/modules/valuadBidAdapter_spec.js @@ -0,0 +1,602 @@ +import { expect, util } from 'chai'; +import * as sinon from 'sinon'; +import { spec, _VALUAD } from 'modules/valuadBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { BANNER, VIDEO, NATIVE } from 'src/mediaTypes.js'; +import { deepClone, generateUUID } from 'src/utils.js'; +import { config } from 'src/config.js'; +import * as utils from 'src/utils.js'; +import * as gptUtils from 'libraries/gptUtils/gptUtils.js'; +import * as refererDetection from 'src/refererDetection.js'; +import * as BoundingClientRect from 'libraries/boundingClientRect/boundingClientRect.js'; + +const ENDPOINT = 'https://rtb.valuad.io/adapter'; +const WON_URL = 'https://hb-dot-valuad.appspot.com/adapter/win'; + +describe('ValuadAdapter', function () { + const adapter = newBidder(spec); + let requestToServer; + let validBidRequests; + let bidderRequest; + let navigatorStub; + + before(function() { + validBidRequests = [ + { + bidder: 'valuad', + params: { + placementId: 'test-placement-id-1' + }, + adUnitCode: 'adunit-code-1', + mediaTypes: { + [BANNER]: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'bid-id-1', + bidderRequestId: 'br-id-1', + auctionId: 'auc-id-1', + transactionId: 'txn-id-1' + } + ]; + + bidderRequest = { + bidderCode: 'valuad', + auctionId: 'auc-id-1', + bidderRequestId: 'br-id-1', + bids: validBidRequests, + refererInfo: { + topmostLocation: 'http://test.com/page', + ref: 'http://referrer.com', + reachedTop: true + }, + timeout: 3000, + gdprConsent: { + apiVersion: 2, + gdprApplies: true, + consentString: 'test-consent-string', + allowAuctionWithoutConsent: false + }, + uspConsent: '1YN-', + ortb2: { + regs: { + gpp: 'test-gpp-string', + gpp_sid: [7], + ext: { + dsa: { behalf: 'advertiser', paid: 'advertiser' } + } + }, + site: { + ext: { + data: { pageType: 'article' } + } + } + } + }; + + navigatorStub = { userAgent: 'Test User Agent', language: 'en-US' }; + }); + + describe('inherited functions', function () { + it('should exist and be a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + bidder: 'valuad', + params: { + placementId: 'test-placement-id' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + [BANNER]: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }; + + it('should return true for a valid banner bid request', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true for a valid video bid request', function () { + let videoBid = deepClone(bid); + videoBid.mediaTypes = { + [VIDEO]: { + playerSize: [[640, 480]] + } + }; + expect(spec.isBidRequestValid(videoBid)).to.equal(true); + }); + + it('should return true for a valid native bid request', function () { + let nativeBid = deepClone(bid); + nativeBid.mediaTypes = { + [NATIVE]: { + title: { required: true, len: 140 }, + image: { required: true, sizes: [300, 250] }, + } + }; + expect(spec.isBidRequestValid(nativeBid)).to.equal(true); + }); + + it('should return false when placementId is missing', function () { + let invalidBid = deepClone(bid); + delete invalidBid.params.placementId; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when params are missing', function () { + let invalidBid = deepClone(bid); + delete invalidBid.params; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when bidId is missing', function () { + let invalidBid = deepClone(bid); + delete invalidBid.bidId; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when mediaTypes is missing', function () { + let invalidBid = deepClone(bid); + delete invalidBid.mediaTypes; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when banner sizes are missing', function () { + let invalidBid = deepClone(bid); + delete invalidBid.mediaTypes[BANNER].sizes; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when video playerSize is missing', function () { + let invalidBid = deepClone(bid); + invalidBid.mediaTypes = { + [VIDEO]: {} + }; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let sandbox; + let clock; + let getWindowTopStub; + let getWindowSelfStub; + let detectRefererStub; + let getGptSlotInfoStub; + let getConfigStub; + let getBoundingClientRectStub; + let performanceStub; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + clock = sinon.useFakeTimers(); + + getWindowTopStub = sandbox.stub(utils, 'getWindowTop').returns({ location: { href: 'http://test.com/page' }, document: { referrer: 'http://referrer.com' } }); + getWindowSelfStub = sandbox.stub(utils, 'getWindowSelf').returns({ location: { href: 'http://test.com/page' }, document: { referrer: 'http://referrer.com' }, innerWidth: 1200, innerHeight: 800, screen: { width: 1920, height: 1080 } }); + sandbox.stub(utils, 'canAccessWindowTop').returns(true); + sandbox.stub(utils, 'getDNT').returns(false); + sandbox.stub(utils, 'generateUUID').returns('test-uuid'); + + detectRefererStub = sandbox.stub(refererDetection, 'parseDomain').returns('test.com'); + + getGptSlotInfoStub = sandbox.stub(gptUtils, 'getGptSlotInfoForAdUnitCode').returns({ gptSlot: '/123/adunit' }); + + getConfigStub = sandbox.stub(config, 'getConfig'); + getConfigStub.withArgs('coppa').returns(false); + + getBoundingClientRectStub = sandbox.stub(BoundingClientRect, 'getBoundingClientRect').returns({ left: 10, top: 20, right: 310, bottom: 270, width: 300, height: 250 }); + + performanceStub = { timing: { navigationStart: 100, domContentLoadedEventEnd: 1000 } }; + Object.defineProperty(global, 'performance', { value: performanceStub, configurable: true, writable: true }); + + Object.defineProperty(global, 'navigator', { value: navigatorStub, configurable: true, writable: true }); + + _VALUAD.pageviewId = 'test-pageview-id'; + _VALUAD.sessionId = 'test-session-id'; + _VALUAD.sessionStartTime = 1678886400000; + _VALUAD.pageLoadTime = 900; + _VALUAD.userActivity = { lastActivityTime: 1678886405000, pageviewCount: 1 }; + + requestToServer = spec.buildRequests(validBidRequests, bidderRequest)[0]; + }); + + afterEach(function () { + sandbox.restore(); + clock.restore(); + delete global.performance; + delete global.navigator; + }); + + it('should return a valid server request object', function () { + expect(requestToServer).to.exist; + expect(requestToServer).to.be.an('object'); + expect(requestToServer.method).to.equal('POST'); + expect(requestToServer.url).to.equal(ENDPOINT); + expect(requestToServer.data).to.be.a('object'); + }); + + it('should build a correct ORTB request payload', function () { + const payload = requestToServer.data; + + expect(payload.id).to.be.a('string'); + expect(payload.imp).to.be.an('array').with.lengthOf(1); + expect(payload.cur).to.deep.equal(['USD']); + expect(payload.tmax).to.equal(bidderRequest.timeout); + + expect(payload.site).to.exist; + expect(payload.site.domain).to.equal('test.com'); + expect(payload.site.page).to.equal(bidderRequest.refererInfo.topmostLocation); + expect(payload.site.referrer).to.equal(bidderRequest.refererInfo.ref); + expect(payload.site.top).to.equal(true); + expect(payload.site.ext.data.valuad_rtd.pageviewId).to.equal('test-pageview-id'); + expect(payload.site.ext.data.valuad_rtd.session.id).to.equal('test-session-id'); + expect(payload.site.ext.data.valuad_rtd.features.dom_loading).to.equal(900); + expect(payload.site.ext.data.pageType).to.equal('article'); + + expect(payload.device).to.exist; + expect(payload.device.userAgent).to.equal(navigatorStub.userAgent); + expect(payload.device.language).to.equal(navigatorStub.language); + expect(payload.device.dnt).to.equal(0); + expect(payload.device.js).to.equal(1); + expect(payload.device.w).to.equal(1920); + expect(payload.device.h).to.equal(1080); + expect(payload.device.ext.vpw).to.be.a('number'); + expect(payload.device.ext.vph).to.be.a('number'); + + expect(payload.regs).to.exist; + expect(payload.regs.gdpr).to.equal(1); + expect(payload.regs.coppa).to.equal(0); + expect(payload.regs.us_privacy).to.equal(bidderRequest.uspConsent); + expect(payload.regs.ext.gdpr_conset).to.equal(bidderRequest.gdprConsent.consentString); + expect(payload.regs.ext.gpp).to.equal(bidderRequest.ortb2.regs.gpp); + expect(payload.regs.ext.gppSid).to.deep.equal(bidderRequest.ortb2.regs.gpp_sid); + expect(payload.regs.ext.dsa).to.deep.equal(bidderRequest.ortb2.regs.ext.dsa); + + expect(payload.ext.params).to.deep.equal(validBidRequests[0].params); + + const imp = payload.imp[0]; + expect(imp.id).to.equal(validBidRequests[0].bidId); + expect(imp.banner).to.exist; + expect(imp.banner.format).to.be.an('array').with.lengthOf(2); + expect(imp.banner.format[0]).to.deep.equal({ w: 300, h: 250 }); + expect(imp.ext.data.adserver.name).to.equal('gam'); + expect(imp.ext.data.adserver.adslot).to.equal('/123/adunit'); + expect(imp.ext.data.pbadslot).to.equal('/123/adunit'); + expect(imp.ext.gpid).to.equal('/123/adunit'); + }); + + it('should include schain if present', function () { + let bidWithSchain = deepClone(validBidRequests); + bidWithSchain[0].schain = { ver: '1.0', complete: 1, nodes: [] }; + let reqWithSchain = deepClone(bidderRequest); + reqWithSchain.bids = bidWithSchain; + + const request = spec.buildRequests(bidWithSchain, reqWithSchain); + const payload = request[0].data; + expect(payload.source.ext.schain).to.deep.equal(bidWithSchain[0].schain); + }); + + it('should include eids if present', function () { + let bidWithEids = deepClone(validBidRequests); + bidWithEids[0].userIdAsEids = [{ source: 'pubcid.org', uids: [{ id: 'test-pubcid' }] }]; + let reqWithEids = deepClone(bidderRequest); + reqWithEids.bids = bidWithEids; + + const request = spec.buildRequests(bidWithEids, reqWithEids); + const payload = request[0].data; + expect(payload.user.ext.eids).to.deep.equal(bidWithEids[0].userIdAsEids); + }); + + it('should handle floors correctly', function () { + let bidWithFloor = deepClone(validBidRequests); + bidWithFloor[0].getFloor = sandbox.stub().returns({ currency: 'USD', floor: 1.50 }); + let reqWithFloor = deepClone(bidderRequest); + reqWithFloor.bids = bidWithFloor; + + const request = spec.buildRequests(bidWithFloor, reqWithFloor); + const payload = request[0].data; + expect(payload.imp[0].bidfloor).to.equal(1.50); + expect(payload.imp[0].bidfloorcur).to.equal('USD'); + sinon.assert.calledWith(bidWithFloor[0].getFloor, { currency: 'USD', mediaType: BANNER, size: [300, 250] }); + }); + + it('should handle video params correctly', function () { + let videoBid = deepClone(validBidRequests[0]); + videoBid.mediaTypes = { + [VIDEO]: { + playerSize: [[640, 480]], + mimes: ['video/mp4'], + protocols: [2, 3], + minduration: 5, + maxduration: 30, + startdelay: 0, + placement: 1, + linearity: 1, + skip: 1, + skipmin: 5, + skipafter: 10, + playbackmethod: [1, 3], + api: [1, 2] + } + }; + let videoReq = deepClone(bidderRequest); + videoReq.bids = [videoBid]; + + const request = spec.buildRequests([videoBid], videoReq); + const payload = request[0].data; + const imp = payload.imp[0]; + + expect(imp.video).to.exist; + expect(imp.video.w).to.equal(640); + expect(imp.video.h).to.equal(480); + expect(imp.video.mimes).to.deep.equal(['video/mp4']); + expect(imp.video.protocols).to.deep.equal([2, 3]); + expect(imp.video.minduration).to.equal(5); + expect(imp.video.maxduration).to.equal(30); + }); + + it('should handle native params correctly', function () { + let nativeBid = deepClone(validBidRequests[0]); + nativeBid.mediaTypes = { + [NATIVE]: { + title: { required: true, len: 100 }, + image: { required: true, sizes: [1200, 627], mimes: ['image/jpeg'] }, + } + }; + let nativeReq = deepClone(bidderRequest); + nativeReq.bids = [nativeBid]; + + const request = spec.buildRequests([nativeBid], nativeReq); + const payload = request[0].data; + const imp = payload.imp[0]; + + expect(imp.native).to.exist; + expect(imp.native.ver).to.equal('1.2'); + expect(imp.native.assets).to.be.an('array').with.lengthOf(2); + expect(imp.native.assets[0].title.len).to.equal(100); + expect(imp.native.assets[1].img.w).to.equal(1200); + expect(imp.native.assets[1].img.h).to.equal(627); + }); + }); + + describe('interpretResponse', function () { + let serverResponse; + + beforeEach(function() { + Object.defineProperty(global, 'navigator', { value: navigatorStub, configurable: true, writable: true }); + + serverResponse = { + body: { + id: 'test-response-id', + seatbid: [ + { + seat: 'valuad', + bid: [ + { + id: 'test-bid-id', + impid: 'bid-id-1', + price: 1.50, + adm: '', + crid: 'creative-id-1', + mtype: 1, + w: 300, + h: 250, + adomain: ['advertiser.com'], + ext: { + prebid: { + type: BANNER + } + } + } + ] + } + ], + cur: 'USD', + ext: { + valuad: { serverInfo: 'some data' } + } + } + }; + }); + + afterEach(function() { + delete global.navigator; + }); + + it('should return an array of valid bid responses', function () { + expect(requestToServer).to.exist; + const bids = spec.interpretResponse(serverResponse, requestToServer); + + expect(bids).to.be.an('array').with.lengthOf(1); + const bid = bids[0]; + + expect(bid.requestId).to.equal('bid-id-1'); + expect(bid.cpm).to.equal(1.50); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ad).to.equal(''); + expect(bid.creativeId).to.equal('creative-id-1'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(30); + expect(bid.mediaType).to.equal(BANNER); + expect(bid.meta.advertiserDomains).to.deep.equal(['advertiser.com']); + expect(bid.vid).to.equal('test-placement-id-1'); + }); + + it('should return an empty array if seatbid is missing', function () { + let responseNoSeatbid = deepClone(serverResponse); + delete responseNoSeatbid.body.seatbid; + const bids = spec.interpretResponse(responseNoSeatbid, requestToServer); + expect(bids).to.be.an('array').with.lengthOf(0); + }); + + it('should return an empty array if bid array is empty', function () { + let responseEmptyBid = deepClone(serverResponse); + responseEmptyBid.body.seatbid[0].bid = []; + const bids = spec.interpretResponse(responseEmptyBid, requestToServer); + expect(bids).to.be.an('array').with.lengthOf(0); + }); + + it('should throw error if response body is missing', function () { + let responseNoBody = { body: null }; + const fn = () => spec.interpretResponse(responseNoBody, requestToServer); + expect(fn).to.throw(); + }); + + it('should populate _VALUAD.serverData if ext.valuad exists in response', function () { + _VALUAD.serverData = undefined; + spec.interpretResponse(serverResponse, requestToServer); + expect(_VALUAD.serverData).to.deep.equal({ serverInfo: 'some data' }); + }); + + it('should not change _VALUAD.serverData if ext.valuad is missing', function () { + _VALUAD.serverData = { initial: 'value' }; + let responseNoExt = deepClone(serverResponse); + delete responseNoExt.body.ext; + spec.interpretResponse(responseNoExt, requestToServer); + expect(_VALUAD.serverData).to.deep.equal({ initial: 'value' }); + }); + }); + + describe('getUserSyncs', function () { + let serverResponses; + + beforeEach(function() { + serverResponses = [ + { + body: { + id: 'test-response-id', + userSyncs: [ + { type: 'iframe', url: 'https://sync.example.com/iframe?id=1' }, + { type: 'image', url: 'https://sync.example.com/pixel?id=2' } + ] + } + } + ]; + }); + + it('should return correct sync objects if server response has userSyncs', function () { + const syncs = spec.getUserSyncs({}, serverResponses); + expect(syncs).to.be.an('array').with.lengthOf(2); + expect(syncs[0]).to.deep.equal({ type: 'iframe', url: 'https://sync.example.com/iframe?id=1' }); + expect(syncs[1]).to.deep.equal({ type: 'image', url: 'https://sync.example.com/pixel?id=2' }); + }); + + it('should return false if server response is empty', function () { + const syncs = spec.getUserSyncs({}, []); + expect(syncs).to.be.false; + }); + + it('should return false if server response body is empty', function () { + const syncs = spec.getUserSyncs({}, [{ body: '' }]); + expect(syncs).to.be.false; + }); + + it('should return false if userSyncs array is missing in response body', function () { + let responseNoSyncs = deepClone(serverResponses); + delete responseNoSyncs[0].body.userSyncs; + const syncs = spec.getUserSyncs({}, responseNoSyncs); + expect(syncs).to.be.false; + }); + + it('should return false if userSyncs array is empty', function () { + let responseEmptySyncs = deepClone(serverResponses); + responseEmptySyncs[0].body.userSyncs = []; + const syncs = spec.getUserSyncs({}, responseEmptySyncs); + expect(syncs).to.be.an('array').with.lengthOf(0); + }); + }); + + describe('onBidWon', function () { + let triggerPixelStub; + let bidWonEvent; + let sandbox; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + triggerPixelStub = sandbox.stub(utils, 'triggerPixel'); + + bidWonEvent = { + adUnitCode: 'adunit-code-1', + adUnitId: 'adunit-id-1', + auctionId: 'auc-id-1', + bidder: 'valuad', + cpm: 1.50, + currency: 'USD', + originalCpm: 1.50, + originalCurrency: 'USD', + size: '300x250', + vbid: 'server-generated-vbid', + vid: 'test-placement-id-1', + }; + }); + + afterEach(function () { + sandbox.restore(); + }); + + it('should call triggerPixel with the correct URL and encoded data', function () { + spec.onBidWon(bidWonEvent); + + const expectedData = { + adUnitCode: bidWonEvent.adUnitCode, + adUnitId: bidWonEvent.adUnitId, + auctionId: bidWonEvent.auctionId, + bidder: bidWonEvent.bidder, + cpm: bidWonEvent.cpm, + currency: bidWonEvent.currency, + originalCpm: bidWonEvent.originalCpm, + originalCurrency: bidWonEvent.originalCurrency, + size: bidWonEvent.size, + vbid: bidWonEvent.vbid, + vid: bidWonEvent.vid, + }; + const expectedEncodedData = btoa(JSON.stringify(expectedData)); + const expectedUrl = `${WON_URL}?b=${expectedEncodedData}`; + + sinon.assert.calledOnce(triggerPixelStub); + sinon.assert.calledWith(triggerPixelStub, expectedUrl); + }); + + it('should handle missing optional properties in bid object gracefully', function () { + let minimalBid = { + adUnitCode: 'adunit-code-2', + auctionId: 'auc-id-2', + bidder: 'valuad', + cpm: 2.00, + currency: 'USD', + size: '728x90' + }; + + spec.onBidWon(minimalBid); + + const expectedData = { + adUnitCode: minimalBid.adUnitCode, + adUnitId: undefined, + auctionId: minimalBid.auctionId, + bidder: minimalBid.bidder, + cpm: minimalBid.cpm, + currency: minimalBid.currency, + originalCpm: undefined, + originalCurrency: undefined, + size: minimalBid.size, + vbid: undefined, + vid: undefined, + }; + const expectedEncodedData = btoa(JSON.stringify(expectedData)); + const expectedUrl = `${WON_URL}?b=${expectedEncodedData}`; + + sinon.assert.calledOnce(triggerPixelStub); + sinon.assert.calledWith(triggerPixelStub, expectedUrl); + }); + }); +}); From 97cb0af37e68f13425f170998a7673db78b564f6 Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Mon, 21 Apr 2025 16:57:14 +0300 Subject: [PATCH 27/44] Removed video and native and added a test ad unit --- modules/valuadBidAdapter.js | 76 +------------------------------------ modules/valuadBidAdapter.md | 4 +- 2 files changed, 4 insertions(+), 76 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index 95da63b4b69..e16b3b42bb9 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -1,5 +1,5 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { BANNER } from '../src/mediaTypes.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { cleanObj, @@ -152,58 +152,6 @@ function getGdprConsent(bidderRequest) { }); } -function processVideoParams(bid) { - const videoParams = deepAccess(bid, 'mediaTypes.video', {}); - const playerSize = videoParams.playerSize || []; - - return cleanObj({ - mimes: videoParams.mimes, - minduration: videoParams.minduration, - maxduration: videoParams.maxduration, - protocols: videoParams.protocols, - w: playerSize[0]?.[0], - h: playerSize[0]?.[1], - startdelay: videoParams.startdelay, - placement: videoParams.placement, - linearity: videoParams.linearity, - skip: videoParams.skip, - skipmin: videoParams.skipmin, - skipafter: videoParams.skipafter, - playbackmethod: videoParams.playbackmethod, - api: videoParams.api - }); -} - -function processNativeAssets(nativeParams) { - const assets = []; - let id = 1; - - if (nativeParams.title) { - assets.push({ - id: id++, - required: nativeParams.title.required ? 1 : 0, - title: { - len: nativeParams.title.len || 140 - } - }); - } - - if (nativeParams.image) { - assets.push({ - id: id++, - required: nativeParams.image.required ? 1 : 0, - img: { - type: 3, // Main image - w: nativeParams.image.sizes[0], - h: nativeParams.image.sizes[1], - mimes: nativeParams.image.mimes || ['image/jpeg', 'image/png'] - } - }); - } - - return assets; -} - // Enhanced ORTBConverter with additional data const converter = ortbConverter({ context: { @@ -318,8 +266,6 @@ const converter = ortbConverter({ if (mediaType === BANNER) { size = bid.mediaTypes.banner.sizes && bid.mediaTypes.banner.sizes[0]; - } else if (mediaType === VIDEO) { - size = bid.mediaTypes.video.playerSize; } if (size) { @@ -339,20 +285,6 @@ const converter = ortbConverter({ } } - if (bid.mediaTypes?.video) { - imp.video = { - ...imp.video, - ...processVideoParams(bid) - }; - } - - if (bid.mediaTypes?.native) { - imp.native = { - ver: '1.2', - assets: processNativeAssets(bid.mediaTypes.native) - }; - } - return imp; }, @@ -385,10 +317,6 @@ function isBidRequestValid(bid = {}) { 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; } @@ -454,7 +382,7 @@ function onBidWon(bid) { export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO, NATIVE], + supportedMediaTypes: [BANNER], isBidRequestValid, buildRequests, interpretResponse, diff --git a/modules/valuadBidAdapter.md b/modules/valuadBidAdapter.md index 9de018e98e7..d2705a7d8fb 100644 --- a/modules/valuadBidAdapter.md +++ b/modules/valuadBidAdapter.md @@ -14,7 +14,7 @@ Valuad bid adapter supports Banner format only. ```js const adUnits = [{ - code: 'test-div', + code: 'valuad-test-div', mediaTypes: { banner: { sizes: [[300, 250]] @@ -23,7 +23,7 @@ Valuad bid adapter supports Banner format only. bids: [{ bidder: 'valuad', params: { - placementId: 'test', // REQUIRED + placementId: '00000', // REQUIRED } }] }]; From 894fedb3ab9944836f4a0f9849bfa47d907cf725 Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Tue, 22 Apr 2025 10:42:06 +0300 Subject: [PATCH 28/44] Added md file for prebid website --- valuad.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 valuad.md diff --git a/valuad.md b/valuad.md new file mode 100644 index 00000000000..8a1064b9d0e --- /dev/null +++ b/valuad.md @@ -0,0 +1,29 @@ +--- +layout: bidder +title: Valuad +description: Prebid Valuad Bidder Adapter +biddercode: valuad +gpp_sids: usstate_all +usp_supported: true +coppa_supported: true +schain_supported: true +deals_supported: true +floors_supported: true +dchain_supported: false +ortb_blocking_supported: false +media_types: banner +safeframes_ok: true +pbjs: true +pbs: false +sidebarType: 1 +--- + +### Note + +The Valuad Header Bidding adapter requires setup and approval from the Brave team. Please reach out to your account manager or for more information + +### Bid Params + +| Name | Scope | Description | Example | Type | +|---------------|----------|--------------------------------|------------|-----------| +| `placementId` | required | Valuad's platform placement id | `'10000'` | `string` | \ No newline at end of file From 3b8020d74751b0d119a56d5d09237be6536dfe9c Mon Sep 17 00:00:00 2001 From: natanavra Date: Tue, 22 Apr 2025 11:33:30 +0300 Subject: [PATCH 29/44] Valuad bid adapter initial release --- modules/valuadBidAdapter.js | 392 ++++++++++++++ modules/valuadBidAdapter.md | 30 + test/spec/modules/valuadBidAdapter_spec.js | 602 +++++++++++++++++++++ 3 files changed, 1024 insertions(+) create mode 100644 modules/valuadBidAdapter.js create mode 100644 modules/valuadBidAdapter.md create mode 100644 test/spec/modules/valuadBidAdapter_spec.js diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js new file mode 100644 index 00000000000..e16b3b42bb9 --- /dev/null +++ b/modules/valuadBidAdapter.js @@ -0,0 +1,392 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { + cleanObj, + deepAccess, + deepSetValue, + generateUUID, + getWindowSelf, + getWindowTop, + canAccessWindowTop, + getDNT, + logInfo, + triggerPixel, +} from '../src/utils.js'; +import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; +import { config } from '../src/config.js'; +import { parseDomain } from '../src/refererDetection.js'; +import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; + +const BIDDER_CODE = 'valuad'; +const AD_URL = 'https://rtb.valuad.io/adapter'; +const WON_URL = 'https://hb-dot-valuad.appspot.com/adapter/win'; + +export const _VALUAD = (function() { + const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); + + w.VALUAD = w.VALUAD || {}; + w.VALUAD.pageviewId = w.VALUAD.pageviewId || generateUUID(); + w.VALUAD.sessionId = w.VALUAD.sessionId || generateUUID(); + w.VALUAD.sessionStartTime = w.VALUAD.sessionStartTime || Date.now(); + w.VALUAD.pageLoadTime = w.VALUAD.pageLoadTime || window.performance?.timing?.domContentLoadedEventEnd - window.performance?.timing?.navigationStart; + w.VALUAD.userActivity = w.VALUAD.userActivity || { + lastActivityTime: Date.now(), + pageviewCount: (w.VALUAD.userActivity?.pageviewCount || 0) + 1 + }; + + return w.VALUAD; +})(); + +// Helper functions to enrich data +function getDevice() { + const language = navigator.language ? 'language' : 'userLanguage'; + const deviceInfo = { + userAgent: navigator.userAgent, + language: navigator[language], + dnt: getDNT() ? 1 : 0, + js: 1, + geo: {} + }; + + // Get screen dimensions + if (window.screen) { + deviceInfo.w = window.screen.width; + deviceInfo.h = window.screen.height; + } + + // Get viewport dimensions + deviceInfo.ext = { + vpw: window.innerWidth, + vph: window.innerHeight + }; + + return deviceInfo; +} + +function getSite(bidderRequest) { + const { refererInfo } = bidderRequest; + const siteInfo = { + domain: parseDomain(refererInfo.topmostLocation) || '', + page: refererInfo.topmostLocation || '', + referrer: refererInfo.ref || getWindowSelf().document.referrer || '', + top: refererInfo.reachedTop + }; + + // Add page metadata if available + const meta = document.querySelector('meta[name="keywords"]'); + if (meta && meta.content) { + siteInfo.keywords = meta.content; + } + + return siteInfo; +} + +function getSession() { + return { + id: _VALUAD.sessionId, + startTime: _VALUAD.sessionStartTime, + lastActivityTime: _VALUAD.userActivity.lastActivityTime, + pageviewCount: _VALUAD.userActivity.pageviewCount, + pageLoadTime: _VALUAD.pageLoadTime || 0, + new: _VALUAD.userActivity.pageviewCount === 1 + }; +} + +// Add detailed ad unit position detection +function detectAdUnitPosition(adUnitCode) { + const element = document.getElementById(adUnitCode) || document.getElementById(getGptSlotInfoForAdUnitCode(adUnitCode)?.divId); + if (!element) return null; + + const rect = getBoundingClientRect(element); + const docElement = document.documentElement; + const pageWidth = docElement.clientWidth; + const pageHeight = docElement.scrollHeight; + + return { + x: Math.round(rect.left + window.pageXOffset), + y: Math.round(rect.top + window.pageYOffset), + w: Math.round(rect.width), + h: Math.round(rect.height), + position: `${Math.round(rect.left + window.pageXOffset)}x${Math.round(rect.top + window.pageYOffset)}`, + viewportVisibility: calculateVisibility(rect), + pageSize: `${pageWidth}x${pageHeight}` + }; +} + +function calculateVisibility(rect) { + const windowHeight = window.innerHeight; + const windowWidth = window.innerWidth; + + // Element is not in viewport + if (rect.bottom < 0 || rect.right < 0 || rect.top > windowHeight || rect.left > windowWidth) { + return 0; + } + + // Calculate visible area + const visibleHeight = Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0); + const visibleWidth = Math.min(rect.right, windowWidth) - Math.max(rect.left, 0); + const visibleArea = visibleHeight * visibleWidth; + const totalArea = rect.height * rect.width; + + return totalArea > 0 ? visibleArea / totalArea : 0; +} + +function getGdprConsent(bidderRequest) { + if (!deepAccess(bidderRequest, 'gdprConsent')) { + return false; + } + + const { + apiVersion, + gdprApplies, + consentString, + allowAuctionWithoutConsent + } = bidderRequest.gdprConsent; + + return cleanObj({ + apiVersion, + consentString, + consentRequired: gdprApplies ? 1 : 0, + allowAuctionWithoutConsent: allowAuctionWithoutConsent ? 1 : 0 + }); +} + +// Enhanced ORTBConverter with additional data +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 30 + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + const device = getDevice(); + const site = getSite(bidderRequest); + const session = getSession(); + + const gdpr = getGdprConsent(bidderRequest); + const uspConsent = deepAccess(bidderRequest, 'uspConsent') || ''; + const coppa = config.getConfig('coppa') === true ? 1 : 0; + const { gpp, gpp_sid: gppSid } = deepAccess(bidderRequest, 'ortb2.regs', {}); + const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); + + // Ensure we have required extensions + deepSetValue(request, 'device', {...request.device, ...device}); + deepSetValue(request, 'site', {...request.site, ...site}); + + deepSetValue(request, 'regs', { + gdpr: gdpr.consentRequired || 0, + coppa: coppa, + us_privacy: uspConsent, + ext: { + gdpr_conset: gdpr.consentString || '', + gpp: gpp || '', + gppSid: gppSid || [], + dsa: dsa, + } + }); + + deepSetValue(request, 'site.ext.data.valuad_rtd', { + pageviewId: _VALUAD.pageviewId, + session: session, + features: { + page_dimensions: `${document.documentElement.scrollWidth}x${document.documentElement.scrollHeight}`, + viewport_dimensions: `${window.innerWidth}x${window.innerHeight}`, + user_timestamp: Math.floor(Date.now() / 1000), + dom_loading: window.performance?.timing?.domContentLoadedEventEnd - window.performance?.timing?.navigationStart + } + }); + + // Add bid parameters + if (bidderRequest && bidderRequest.bids && bidderRequest.bids.length) { + deepSetValue(request, 'ext.params', bidderRequest.bids[0].params); + } + + // Set currency to USD + deepSetValue(request, 'cur', ['USD']); + + // Add schain if present + const schain = deepAccess(bidderRequest.bids[0], 'schain'); + if (schain) { + deepSetValue(request, 'source.ext.schain', schain); + } + + // Add eids if present + const eids = deepAccess(bidderRequest.bids[0], 'userIdAsEids'); + if (eids) { + deepSetValue(request, 'user.ext.eids', eids); + } + + const ortb2 = bidderRequest.ortb2 || {}; + if (ortb2.site?.ext?.data) { + deepSetValue(request, 'site.ext.data', { + ...request.site.ext.data, + ...ortb2.site.ext.data + }); + } + + const tmax = bidderRequest.timeout; + if (tmax) { + deepSetValue(request, 'tmax', tmax); + } + + return request; + }, + + imp(buildImp, bid, context) { + const imp = buildImp(bid, context); + + // Add additional impression data + const positionData = detectAdUnitPosition(bid.adUnitCode); + if (positionData) { + deepSetValue(imp, 'ext.data.adg_rtd.adunit_position', positionData.position); + deepSetValue(imp, 'ext.data.viewability', positionData.viewportVisibility); + } + + // GPT information + const gptInfo = getGptSlotInfoForAdUnitCode(bid.adUnitCode); + if (gptInfo) { + deepSetValue(imp, 'ext.data.adserver', { + name: 'gam', + adslot: gptInfo.gptSlot + }); + deepSetValue(imp, 'ext.data.pbadslot', gptInfo.gptSlot); + + // If not already set, add gpid + if (!imp.ext.gpid && gptInfo.gptSlot) { + deepSetValue(imp, 'ext.gpid', gptInfo.gptSlot); + } + } + + // Handle price floors + if (typeof bid.getFloor === 'function') { + try { + const mediaType = Object.keys(bid.mediaTypes)[0]; + let size; + + if (mediaType === BANNER) { + size = bid.mediaTypes.banner.sizes && bid.mediaTypes.banner.sizes[0]; + } + + if (size) { + const floor = bid.getFloor({ + currency: 'USD', + mediaType, + size + }); + + if (floor && !isNaN(floor.floor) && floor.currency === 'USD') { + imp.bidfloor = floor.floor; + imp.bidfloorcur = 'USD'; + } + } + } catch (e) { + logInfo('Valuad: Error getting floor', e); + } + } + + return imp; + }, + + bidResponse(buildBidResponse, bid, context) { + let bidResponse; + try { + bidResponse = buildBidResponse(bid, context); + + if (bidResponse) { + if (bid.vbid) { + bidResponse.vbid = bid.vbid; + } + if (context.bidRequest?.params?.placementId) { + bidResponse.vid = context.bidRequest.params.placementId; + } + } + } catch (e) { + logInfo('[VALUAD CONVERTER] Error calling buildBidResponse:', e, 'Bid:', bid); + return; + } + return bidResponse; + }, +}); + +function isBidRequestValid(bid = {}) { + const { params, bidId, mediaTypes } = bid; + + const foundKeys = bid && bid.params && bid.params.placementId; + let valid = Boolean(bidId && params && foundKeys); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else { + valid = false; + } + + return valid; +} + +function buildRequests(validBidRequests = [], bidderRequest = {}) { + // Add bid-level metadata for our server to use + validBidRequests = validBidRequests.map(req => { + req.valuadMeta = { + pageviewId: _VALUAD.pageviewId, + adUnitPosition: detectAdUnitPosition(req.adUnitCode) + }; + return req; + }); + + const data = converter.toORTB({ validBidRequests, bidderRequest }); + + // Update session data + _VALUAD.userActivity.lastActivityTime = Date.now(); + + return [{ + method: 'POST', + url: AD_URL, + data + }]; +} + +function interpretResponse(response, request) { + // Restore original call, remove logging and safe navigation + const bidResponses = converter.fromORTB({response: response.body, request: request.data}).bids; + + // Restore original server-side data processing + if (response.body && response.body.ext && response.body.ext.valuad) { + _VALUAD.serverData = response.body.ext.valuad; + } + + return bidResponses; +} + +function getUserSyncs(syncOptions, serverResponses) { + if (!serverResponses.length || serverResponses[0].body === '' || !serverResponses[0].body.userSyncs) { + return false; + } + + return serverResponses[0].body.userSyncs.map(sync => ({ + type: sync.type === 'iframe' ? 'iframe' : 'image', + url: sync.url + })); +} + +function onBidWon(bid) { + const { + adUnitCode, adUnitId, auctionId, bidder, cpm, currency, originalCpm, originalCurrency, size, vbid, vid, + } = bid; + const bidStr = JSON.stringify({ + adUnitCode, adUnitId, auctionId, bidder, cpm, currency, originalCpm, originalCurrency, size, vbid, vid, + }); + const encodedBidStr = window.btoa(bidStr); + triggerPixel(WON_URL + '?b=' + encodedBidStr); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + onBidWon, +}; +registerBidder(spec); diff --git a/modules/valuadBidAdapter.md b/modules/valuadBidAdapter.md new file mode 100644 index 00000000000..d2705a7d8fb --- /dev/null +++ b/modules/valuadBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +**Module Name**: Valuad Bid Adapter +**Module Type**: Bidder Adapter +**Maintainer**: natan@valuad.io + +# Description + + +Module that connects to Valuad.io demand sources. +Valuad bid adapter supports Banner format only. + +# Test Parameters + +```js + const adUnits = [{ + code: 'valuad-test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'valuad', + params: { + placementId: '00000', // REQUIRED + } + }] + }]; +``` diff --git a/test/spec/modules/valuadBidAdapter_spec.js b/test/spec/modules/valuadBidAdapter_spec.js new file mode 100644 index 00000000000..8c4bcd791b8 --- /dev/null +++ b/test/spec/modules/valuadBidAdapter_spec.js @@ -0,0 +1,602 @@ +import { expect, util } from 'chai'; +import * as sinon from 'sinon'; +import { spec, _VALUAD } from 'modules/valuadBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { BANNER, VIDEO, NATIVE } from 'src/mediaTypes.js'; +import { deepClone, generateUUID } from 'src/utils.js'; +import { config } from 'src/config.js'; +import * as utils from 'src/utils.js'; +import * as gptUtils from 'libraries/gptUtils/gptUtils.js'; +import * as refererDetection from 'src/refererDetection.js'; +import * as BoundingClientRect from 'libraries/boundingClientRect/boundingClientRect.js'; + +const ENDPOINT = 'https://rtb.valuad.io/adapter'; +const WON_URL = 'https://hb-dot-valuad.appspot.com/adapter/win'; + +describe('ValuadAdapter', function () { + const adapter = newBidder(spec); + let requestToServer; + let validBidRequests; + let bidderRequest; + let navigatorStub; + + before(function() { + validBidRequests = [ + { + bidder: 'valuad', + params: { + placementId: 'test-placement-id-1' + }, + adUnitCode: 'adunit-code-1', + mediaTypes: { + [BANNER]: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'bid-id-1', + bidderRequestId: 'br-id-1', + auctionId: 'auc-id-1', + transactionId: 'txn-id-1' + } + ]; + + bidderRequest = { + bidderCode: 'valuad', + auctionId: 'auc-id-1', + bidderRequestId: 'br-id-1', + bids: validBidRequests, + refererInfo: { + topmostLocation: 'http://test.com/page', + ref: 'http://referrer.com', + reachedTop: true + }, + timeout: 3000, + gdprConsent: { + apiVersion: 2, + gdprApplies: true, + consentString: 'test-consent-string', + allowAuctionWithoutConsent: false + }, + uspConsent: '1YN-', + ortb2: { + regs: { + gpp: 'test-gpp-string', + gpp_sid: [7], + ext: { + dsa: { behalf: 'advertiser', paid: 'advertiser' } + } + }, + site: { + ext: { + data: { pageType: 'article' } + } + } + } + }; + + navigatorStub = { userAgent: 'Test User Agent', language: 'en-US' }; + }); + + describe('inherited functions', function () { + it('should exist and be a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + bidder: 'valuad', + params: { + placementId: 'test-placement-id' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + [BANNER]: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }; + + it('should return true for a valid banner bid request', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true for a valid video bid request', function () { + let videoBid = deepClone(bid); + videoBid.mediaTypes = { + [VIDEO]: { + playerSize: [[640, 480]] + } + }; + expect(spec.isBidRequestValid(videoBid)).to.equal(true); + }); + + it('should return true for a valid native bid request', function () { + let nativeBid = deepClone(bid); + nativeBid.mediaTypes = { + [NATIVE]: { + title: { required: true, len: 140 }, + image: { required: true, sizes: [300, 250] }, + } + }; + expect(spec.isBidRequestValid(nativeBid)).to.equal(true); + }); + + it('should return false when placementId is missing', function () { + let invalidBid = deepClone(bid); + delete invalidBid.params.placementId; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when params are missing', function () { + let invalidBid = deepClone(bid); + delete invalidBid.params; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when bidId is missing', function () { + let invalidBid = deepClone(bid); + delete invalidBid.bidId; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when mediaTypes is missing', function () { + let invalidBid = deepClone(bid); + delete invalidBid.mediaTypes; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when banner sizes are missing', function () { + let invalidBid = deepClone(bid); + delete invalidBid.mediaTypes[BANNER].sizes; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when video playerSize is missing', function () { + let invalidBid = deepClone(bid); + invalidBid.mediaTypes = { + [VIDEO]: {} + }; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let sandbox; + let clock; + let getWindowTopStub; + let getWindowSelfStub; + let detectRefererStub; + let getGptSlotInfoStub; + let getConfigStub; + let getBoundingClientRectStub; + let performanceStub; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + clock = sinon.useFakeTimers(); + + getWindowTopStub = sandbox.stub(utils, 'getWindowTop').returns({ location: { href: 'http://test.com/page' }, document: { referrer: 'http://referrer.com' } }); + getWindowSelfStub = sandbox.stub(utils, 'getWindowSelf').returns({ location: { href: 'http://test.com/page' }, document: { referrer: 'http://referrer.com' }, innerWidth: 1200, innerHeight: 800, screen: { width: 1920, height: 1080 } }); + sandbox.stub(utils, 'canAccessWindowTop').returns(true); + sandbox.stub(utils, 'getDNT').returns(false); + sandbox.stub(utils, 'generateUUID').returns('test-uuid'); + + detectRefererStub = sandbox.stub(refererDetection, 'parseDomain').returns('test.com'); + + getGptSlotInfoStub = sandbox.stub(gptUtils, 'getGptSlotInfoForAdUnitCode').returns({ gptSlot: '/123/adunit' }); + + getConfigStub = sandbox.stub(config, 'getConfig'); + getConfigStub.withArgs('coppa').returns(false); + + getBoundingClientRectStub = sandbox.stub(BoundingClientRect, 'getBoundingClientRect').returns({ left: 10, top: 20, right: 310, bottom: 270, width: 300, height: 250 }); + + performanceStub = { timing: { navigationStart: 100, domContentLoadedEventEnd: 1000 } }; + Object.defineProperty(global, 'performance', { value: performanceStub, configurable: true, writable: true }); + + Object.defineProperty(global, 'navigator', { value: navigatorStub, configurable: true, writable: true }); + + _VALUAD.pageviewId = 'test-pageview-id'; + _VALUAD.sessionId = 'test-session-id'; + _VALUAD.sessionStartTime = 1678886400000; + _VALUAD.pageLoadTime = 900; + _VALUAD.userActivity = { lastActivityTime: 1678886405000, pageviewCount: 1 }; + + requestToServer = spec.buildRequests(validBidRequests, bidderRequest)[0]; + }); + + afterEach(function () { + sandbox.restore(); + clock.restore(); + delete global.performance; + delete global.navigator; + }); + + it('should return a valid server request object', function () { + expect(requestToServer).to.exist; + expect(requestToServer).to.be.an('object'); + expect(requestToServer.method).to.equal('POST'); + expect(requestToServer.url).to.equal(ENDPOINT); + expect(requestToServer.data).to.be.a('object'); + }); + + it('should build a correct ORTB request payload', function () { + const payload = requestToServer.data; + + expect(payload.id).to.be.a('string'); + expect(payload.imp).to.be.an('array').with.lengthOf(1); + expect(payload.cur).to.deep.equal(['USD']); + expect(payload.tmax).to.equal(bidderRequest.timeout); + + expect(payload.site).to.exist; + expect(payload.site.domain).to.equal('test.com'); + expect(payload.site.page).to.equal(bidderRequest.refererInfo.topmostLocation); + expect(payload.site.referrer).to.equal(bidderRequest.refererInfo.ref); + expect(payload.site.top).to.equal(true); + expect(payload.site.ext.data.valuad_rtd.pageviewId).to.equal('test-pageview-id'); + expect(payload.site.ext.data.valuad_rtd.session.id).to.equal('test-session-id'); + expect(payload.site.ext.data.valuad_rtd.features.dom_loading).to.equal(900); + expect(payload.site.ext.data.pageType).to.equal('article'); + + expect(payload.device).to.exist; + expect(payload.device.userAgent).to.equal(navigatorStub.userAgent); + expect(payload.device.language).to.equal(navigatorStub.language); + expect(payload.device.dnt).to.equal(0); + expect(payload.device.js).to.equal(1); + expect(payload.device.w).to.equal(1920); + expect(payload.device.h).to.equal(1080); + expect(payload.device.ext.vpw).to.be.a('number'); + expect(payload.device.ext.vph).to.be.a('number'); + + expect(payload.regs).to.exist; + expect(payload.regs.gdpr).to.equal(1); + expect(payload.regs.coppa).to.equal(0); + expect(payload.regs.us_privacy).to.equal(bidderRequest.uspConsent); + expect(payload.regs.ext.gdpr_conset).to.equal(bidderRequest.gdprConsent.consentString); + expect(payload.regs.ext.gpp).to.equal(bidderRequest.ortb2.regs.gpp); + expect(payload.regs.ext.gppSid).to.deep.equal(bidderRequest.ortb2.regs.gpp_sid); + expect(payload.regs.ext.dsa).to.deep.equal(bidderRequest.ortb2.regs.ext.dsa); + + expect(payload.ext.params).to.deep.equal(validBidRequests[0].params); + + const imp = payload.imp[0]; + expect(imp.id).to.equal(validBidRequests[0].bidId); + expect(imp.banner).to.exist; + expect(imp.banner.format).to.be.an('array').with.lengthOf(2); + expect(imp.banner.format[0]).to.deep.equal({ w: 300, h: 250 }); + expect(imp.ext.data.adserver.name).to.equal('gam'); + expect(imp.ext.data.adserver.adslot).to.equal('/123/adunit'); + expect(imp.ext.data.pbadslot).to.equal('/123/adunit'); + expect(imp.ext.gpid).to.equal('/123/adunit'); + }); + + it('should include schain if present', function () { + let bidWithSchain = deepClone(validBidRequests); + bidWithSchain[0].schain = { ver: '1.0', complete: 1, nodes: [] }; + let reqWithSchain = deepClone(bidderRequest); + reqWithSchain.bids = bidWithSchain; + + const request = spec.buildRequests(bidWithSchain, reqWithSchain); + const payload = request[0].data; + expect(payload.source.ext.schain).to.deep.equal(bidWithSchain[0].schain); + }); + + it('should include eids if present', function () { + let bidWithEids = deepClone(validBidRequests); + bidWithEids[0].userIdAsEids = [{ source: 'pubcid.org', uids: [{ id: 'test-pubcid' }] }]; + let reqWithEids = deepClone(bidderRequest); + reqWithEids.bids = bidWithEids; + + const request = spec.buildRequests(bidWithEids, reqWithEids); + const payload = request[0].data; + expect(payload.user.ext.eids).to.deep.equal(bidWithEids[0].userIdAsEids); + }); + + it('should handle floors correctly', function () { + let bidWithFloor = deepClone(validBidRequests); + bidWithFloor[0].getFloor = sandbox.stub().returns({ currency: 'USD', floor: 1.50 }); + let reqWithFloor = deepClone(bidderRequest); + reqWithFloor.bids = bidWithFloor; + + const request = spec.buildRequests(bidWithFloor, reqWithFloor); + const payload = request[0].data; + expect(payload.imp[0].bidfloor).to.equal(1.50); + expect(payload.imp[0].bidfloorcur).to.equal('USD'); + sinon.assert.calledWith(bidWithFloor[0].getFloor, { currency: 'USD', mediaType: BANNER, size: [300, 250] }); + }); + + it('should handle video params correctly', function () { + let videoBid = deepClone(validBidRequests[0]); + videoBid.mediaTypes = { + [VIDEO]: { + playerSize: [[640, 480]], + mimes: ['video/mp4'], + protocols: [2, 3], + minduration: 5, + maxduration: 30, + startdelay: 0, + placement: 1, + linearity: 1, + skip: 1, + skipmin: 5, + skipafter: 10, + playbackmethod: [1, 3], + api: [1, 2] + } + }; + let videoReq = deepClone(bidderRequest); + videoReq.bids = [videoBid]; + + const request = spec.buildRequests([videoBid], videoReq); + const payload = request[0].data; + const imp = payload.imp[0]; + + expect(imp.video).to.exist; + expect(imp.video.w).to.equal(640); + expect(imp.video.h).to.equal(480); + expect(imp.video.mimes).to.deep.equal(['video/mp4']); + expect(imp.video.protocols).to.deep.equal([2, 3]); + expect(imp.video.minduration).to.equal(5); + expect(imp.video.maxduration).to.equal(30); + }); + + it('should handle native params correctly', function () { + let nativeBid = deepClone(validBidRequests[0]); + nativeBid.mediaTypes = { + [NATIVE]: { + title: { required: true, len: 100 }, + image: { required: true, sizes: [1200, 627], mimes: ['image/jpeg'] }, + } + }; + let nativeReq = deepClone(bidderRequest); + nativeReq.bids = [nativeBid]; + + const request = spec.buildRequests([nativeBid], nativeReq); + const payload = request[0].data; + const imp = payload.imp[0]; + + expect(imp.native).to.exist; + expect(imp.native.ver).to.equal('1.2'); + expect(imp.native.assets).to.be.an('array').with.lengthOf(2); + expect(imp.native.assets[0].title.len).to.equal(100); + expect(imp.native.assets[1].img.w).to.equal(1200); + expect(imp.native.assets[1].img.h).to.equal(627); + }); + }); + + describe('interpretResponse', function () { + let serverResponse; + + beforeEach(function() { + Object.defineProperty(global, 'navigator', { value: navigatorStub, configurable: true, writable: true }); + + serverResponse = { + body: { + id: 'test-response-id', + seatbid: [ + { + seat: 'valuad', + bid: [ + { + id: 'test-bid-id', + impid: 'bid-id-1', + price: 1.50, + adm: '', + crid: 'creative-id-1', + mtype: 1, + w: 300, + h: 250, + adomain: ['advertiser.com'], + ext: { + prebid: { + type: BANNER + } + } + } + ] + } + ], + cur: 'USD', + ext: { + valuad: { serverInfo: 'some data' } + } + } + }; + }); + + afterEach(function() { + delete global.navigator; + }); + + it('should return an array of valid bid responses', function () { + expect(requestToServer).to.exist; + const bids = spec.interpretResponse(serverResponse, requestToServer); + + expect(bids).to.be.an('array').with.lengthOf(1); + const bid = bids[0]; + + expect(bid.requestId).to.equal('bid-id-1'); + expect(bid.cpm).to.equal(1.50); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ad).to.equal(''); + expect(bid.creativeId).to.equal('creative-id-1'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(30); + expect(bid.mediaType).to.equal(BANNER); + expect(bid.meta.advertiserDomains).to.deep.equal(['advertiser.com']); + expect(bid.vid).to.equal('test-placement-id-1'); + }); + + it('should return an empty array if seatbid is missing', function () { + let responseNoSeatbid = deepClone(serverResponse); + delete responseNoSeatbid.body.seatbid; + const bids = spec.interpretResponse(responseNoSeatbid, requestToServer); + expect(bids).to.be.an('array').with.lengthOf(0); + }); + + it('should return an empty array if bid array is empty', function () { + let responseEmptyBid = deepClone(serverResponse); + responseEmptyBid.body.seatbid[0].bid = []; + const bids = spec.interpretResponse(responseEmptyBid, requestToServer); + expect(bids).to.be.an('array').with.lengthOf(0); + }); + + it('should throw error if response body is missing', function () { + let responseNoBody = { body: null }; + const fn = () => spec.interpretResponse(responseNoBody, requestToServer); + expect(fn).to.throw(); + }); + + it('should populate _VALUAD.serverData if ext.valuad exists in response', function () { + _VALUAD.serverData = undefined; + spec.interpretResponse(serverResponse, requestToServer); + expect(_VALUAD.serverData).to.deep.equal({ serverInfo: 'some data' }); + }); + + it('should not change _VALUAD.serverData if ext.valuad is missing', function () { + _VALUAD.serverData = { initial: 'value' }; + let responseNoExt = deepClone(serverResponse); + delete responseNoExt.body.ext; + spec.interpretResponse(responseNoExt, requestToServer); + expect(_VALUAD.serverData).to.deep.equal({ initial: 'value' }); + }); + }); + + describe('getUserSyncs', function () { + let serverResponses; + + beforeEach(function() { + serverResponses = [ + { + body: { + id: 'test-response-id', + userSyncs: [ + { type: 'iframe', url: 'https://sync.example.com/iframe?id=1' }, + { type: 'image', url: 'https://sync.example.com/pixel?id=2' } + ] + } + } + ]; + }); + + it('should return correct sync objects if server response has userSyncs', function () { + const syncs = spec.getUserSyncs({}, serverResponses); + expect(syncs).to.be.an('array').with.lengthOf(2); + expect(syncs[0]).to.deep.equal({ type: 'iframe', url: 'https://sync.example.com/iframe?id=1' }); + expect(syncs[1]).to.deep.equal({ type: 'image', url: 'https://sync.example.com/pixel?id=2' }); + }); + + it('should return false if server response is empty', function () { + const syncs = spec.getUserSyncs({}, []); + expect(syncs).to.be.false; + }); + + it('should return false if server response body is empty', function () { + const syncs = spec.getUserSyncs({}, [{ body: '' }]); + expect(syncs).to.be.false; + }); + + it('should return false if userSyncs array is missing in response body', function () { + let responseNoSyncs = deepClone(serverResponses); + delete responseNoSyncs[0].body.userSyncs; + const syncs = spec.getUserSyncs({}, responseNoSyncs); + expect(syncs).to.be.false; + }); + + it('should return false if userSyncs array is empty', function () { + let responseEmptySyncs = deepClone(serverResponses); + responseEmptySyncs[0].body.userSyncs = []; + const syncs = spec.getUserSyncs({}, responseEmptySyncs); + expect(syncs).to.be.an('array').with.lengthOf(0); + }); + }); + + describe('onBidWon', function () { + let triggerPixelStub; + let bidWonEvent; + let sandbox; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + triggerPixelStub = sandbox.stub(utils, 'triggerPixel'); + + bidWonEvent = { + adUnitCode: 'adunit-code-1', + adUnitId: 'adunit-id-1', + auctionId: 'auc-id-1', + bidder: 'valuad', + cpm: 1.50, + currency: 'USD', + originalCpm: 1.50, + originalCurrency: 'USD', + size: '300x250', + vbid: 'server-generated-vbid', + vid: 'test-placement-id-1', + }; + }); + + afterEach(function () { + sandbox.restore(); + }); + + it('should call triggerPixel with the correct URL and encoded data', function () { + spec.onBidWon(bidWonEvent); + + const expectedData = { + adUnitCode: bidWonEvent.adUnitCode, + adUnitId: bidWonEvent.adUnitId, + auctionId: bidWonEvent.auctionId, + bidder: bidWonEvent.bidder, + cpm: bidWonEvent.cpm, + currency: bidWonEvent.currency, + originalCpm: bidWonEvent.originalCpm, + originalCurrency: bidWonEvent.originalCurrency, + size: bidWonEvent.size, + vbid: bidWonEvent.vbid, + vid: bidWonEvent.vid, + }; + const expectedEncodedData = btoa(JSON.stringify(expectedData)); + const expectedUrl = `${WON_URL}?b=${expectedEncodedData}`; + + sinon.assert.calledOnce(triggerPixelStub); + sinon.assert.calledWith(triggerPixelStub, expectedUrl); + }); + + it('should handle missing optional properties in bid object gracefully', function () { + let minimalBid = { + adUnitCode: 'adunit-code-2', + auctionId: 'auc-id-2', + bidder: 'valuad', + cpm: 2.00, + currency: 'USD', + size: '728x90' + }; + + spec.onBidWon(minimalBid); + + const expectedData = { + adUnitCode: minimalBid.adUnitCode, + adUnitId: undefined, + auctionId: minimalBid.auctionId, + bidder: minimalBid.bidder, + cpm: minimalBid.cpm, + currency: minimalBid.currency, + originalCpm: undefined, + originalCurrency: undefined, + size: minimalBid.size, + vbid: undefined, + vid: undefined, + }; + const expectedEncodedData = btoa(JSON.stringify(expectedData)); + const expectedUrl = `${WON_URL}?b=${expectedEncodedData}`; + + sinon.assert.calledOnce(triggerPixelStub); + sinon.assert.calledWith(triggerPixelStub, expectedUrl); + }); + }); +}); From 7985dbe19824411305ed9dcb9ba623e51ac93be9 Mon Sep 17 00:00:00 2001 From: natanavra Date: Tue, 22 Apr 2025 12:02:16 +0300 Subject: [PATCH 30/44] Update window size calls to use utils --- modules/valuadBidAdapter.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index e16b3b42bb9..86cda06f586 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -12,6 +12,7 @@ import { getDNT, logInfo, triggerPixel, + getWinDimensions, } from '../src/utils.js'; import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; import { config } from '../src/config.js'; @@ -49,16 +50,17 @@ function getDevice() { geo: {} }; + const { innerWidth: windowWidth, innerHeight: windowHeight, screen } = getWinDimensions(); // Get screen dimensions if (window.screen) { - deviceInfo.w = window.screen.width; - deviceInfo.h = window.screen.height; + deviceInfo.w = screen.width; + deviceInfo.h = screen.height; } // Get viewport dimensions deviceInfo.ext = { - vpw: window.innerWidth, - vph: window.innerHeight + vpw: windowWidth, + vph: windowHeight }; return deviceInfo; @@ -115,8 +117,7 @@ function detectAdUnitPosition(adUnitCode) { } function calculateVisibility(rect) { - const windowHeight = window.innerHeight; - const windowWidth = window.innerWidth; + const { innerWidth: windowWidth, innerHeight: windowHeight } = getWinDimensions(); // Element is not in viewport if (rect.bottom < 0 || rect.right < 0 || rect.top > windowHeight || rect.left > windowWidth) { @@ -186,12 +187,13 @@ const converter = ortbConverter({ } }); + const { innerWidth: windowWidth, innerHeight: windowHeight } = getWinDimensions(); deepSetValue(request, 'site.ext.data.valuad_rtd', { pageviewId: _VALUAD.pageviewId, session: session, features: { page_dimensions: `${document.documentElement.scrollWidth}x${document.documentElement.scrollHeight}`, - viewport_dimensions: `${window.innerWidth}x${window.innerHeight}`, + viewport_dimensions: `${windowWidth}x${windowHeight}`, user_timestamp: Math.floor(Date.now() / 1000), dom_loading: window.performance?.timing?.domContentLoadedEventEnd - window.performance?.timing?.navigationStart } From fa7d511e98cf07e2ab10394a31643a6ebbde9847 Mon Sep 17 00:00:00 2001 From: natanavra Date: Tue, 22 Apr 2025 12:47:14 +0300 Subject: [PATCH 31/44] Update valuad.md based on adagio, adipolo and copper6ssp --- valuad.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/valuad.md b/valuad.md index 8a1064b9d0e..233baa5d02d 100644 --- a/valuad.md +++ b/valuad.md @@ -3,7 +3,12 @@ layout: bidder title: Valuad description: Prebid Valuad Bidder Adapter biddercode: valuad +pbjs: true +pbs: false +prebid_member: false +userId: none gpp_sids: usstate_all +tcfeu_supported: true usp_supported: true coppa_supported: true schain_supported: true @@ -13,17 +18,17 @@ dchain_supported: false ortb_blocking_supported: false media_types: banner safeframes_ok: true -pbjs: true -pbs: false +ortb_blocking_supported: false sidebarType: 1 --- ### Note -The Valuad Header Bidding adapter requires setup and approval from the Brave team. Please reach out to your account manager or for more information +The Valuad Header Bidding adapter requires setup and approval from Valuad. +Please reach out to your account manager or for more information ### Bid Params | Name | Scope | Description | Example | Type | |---------------|----------|--------------------------------|------------|-----------| -| `placementId` | required | Valuad's platform placement id | `'10000'` | `string` | \ No newline at end of file +| `placementId` | required | Placement ID provided by VAluad | `'10000'` | `string` | From f7a5e687955d3c8fc95881510a25324b7c61a86f Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Tue, 22 Apr 2025 13:27:01 +0300 Subject: [PATCH 32/44] Removed checks for video and native --- test/spec/modules/valuadBidAdapter_spec.js | 89 +--------------------- 1 file changed, 1 insertion(+), 88 deletions(-) diff --git a/test/spec/modules/valuadBidAdapter_spec.js b/test/spec/modules/valuadBidAdapter_spec.js index 8c4bcd791b8..4eb06a91895 100644 --- a/test/spec/modules/valuadBidAdapter_spec.js +++ b/test/spec/modules/valuadBidAdapter_spec.js @@ -2,7 +2,7 @@ import { expect, util } from 'chai'; import * as sinon from 'sinon'; import { spec, _VALUAD } from 'modules/valuadBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import { BANNER, VIDEO, NATIVE } from 'src/mediaTypes.js'; +import { BANNER } from 'src/mediaTypes.js'; import { deepClone, generateUUID } from 'src/utils.js'; import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; @@ -104,27 +104,6 @@ describe('ValuadAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return true for a valid video bid request', function () { - let videoBid = deepClone(bid); - videoBid.mediaTypes = { - [VIDEO]: { - playerSize: [[640, 480]] - } - }; - expect(spec.isBidRequestValid(videoBid)).to.equal(true); - }); - - it('should return true for a valid native bid request', function () { - let nativeBid = deepClone(bid); - nativeBid.mediaTypes = { - [NATIVE]: { - title: { required: true, len: 140 }, - image: { required: true, sizes: [300, 250] }, - } - }; - expect(spec.isBidRequestValid(nativeBid)).to.equal(true); - }); - it('should return false when placementId is missing', function () { let invalidBid = deepClone(bid); delete invalidBid.params.placementId; @@ -154,14 +133,6 @@ describe('ValuadAdapter', function () { delete invalidBid.mediaTypes[BANNER].sizes; expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); - - it('should return false when video playerSize is missing', function () { - let invalidBid = deepClone(bid); - invalidBid.mediaTypes = { - [VIDEO]: {} - }; - expect(spec.isBidRequestValid(invalidBid)).to.equal(false); - }); }); describe('buildRequests', function () { @@ -307,64 +278,6 @@ describe('ValuadAdapter', function () { expect(payload.imp[0].bidfloorcur).to.equal('USD'); sinon.assert.calledWith(bidWithFloor[0].getFloor, { currency: 'USD', mediaType: BANNER, size: [300, 250] }); }); - - it('should handle video params correctly', function () { - let videoBid = deepClone(validBidRequests[0]); - videoBid.mediaTypes = { - [VIDEO]: { - playerSize: [[640, 480]], - mimes: ['video/mp4'], - protocols: [2, 3], - minduration: 5, - maxduration: 30, - startdelay: 0, - placement: 1, - linearity: 1, - skip: 1, - skipmin: 5, - skipafter: 10, - playbackmethod: [1, 3], - api: [1, 2] - } - }; - let videoReq = deepClone(bidderRequest); - videoReq.bids = [videoBid]; - - const request = spec.buildRequests([videoBid], videoReq); - const payload = request[0].data; - const imp = payload.imp[0]; - - expect(imp.video).to.exist; - expect(imp.video.w).to.equal(640); - expect(imp.video.h).to.equal(480); - expect(imp.video.mimes).to.deep.equal(['video/mp4']); - expect(imp.video.protocols).to.deep.equal([2, 3]); - expect(imp.video.minduration).to.equal(5); - expect(imp.video.maxduration).to.equal(30); - }); - - it('should handle native params correctly', function () { - let nativeBid = deepClone(validBidRequests[0]); - nativeBid.mediaTypes = { - [NATIVE]: { - title: { required: true, len: 100 }, - image: { required: true, sizes: [1200, 627], mimes: ['image/jpeg'] }, - } - }; - let nativeReq = deepClone(bidderRequest); - nativeReq.bids = [nativeBid]; - - const request = spec.buildRequests([nativeBid], nativeReq); - const payload = request[0].data; - const imp = payload.imp[0]; - - expect(imp.native).to.exist; - expect(imp.native.ver).to.equal('1.2'); - expect(imp.native.assets).to.be.an('array').with.lengthOf(2); - expect(imp.native.assets[0].title.len).to.equal(100); - expect(imp.native.assets[1].img.w).to.equal(1200); - expect(imp.native.assets[1].img.h).to.equal(627); - }); }); describe('interpretResponse', function () { From 768dcae19e33fd39df6b1d15cdc73e1d145655bc Mon Sep 17 00:00:00 2001 From: natanavra Date: Tue, 22 Apr 2025 13:51:38 +0300 Subject: [PATCH 33/44] Update window size calls to use util --- modules/valuadBidAdapter.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index e16b3b42bb9..86cda06f586 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -12,6 +12,7 @@ import { getDNT, logInfo, triggerPixel, + getWinDimensions, } from '../src/utils.js'; import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; import { config } from '../src/config.js'; @@ -49,16 +50,17 @@ function getDevice() { geo: {} }; + const { innerWidth: windowWidth, innerHeight: windowHeight, screen } = getWinDimensions(); // Get screen dimensions if (window.screen) { - deviceInfo.w = window.screen.width; - deviceInfo.h = window.screen.height; + deviceInfo.w = screen.width; + deviceInfo.h = screen.height; } // Get viewport dimensions deviceInfo.ext = { - vpw: window.innerWidth, - vph: window.innerHeight + vpw: windowWidth, + vph: windowHeight }; return deviceInfo; @@ -115,8 +117,7 @@ function detectAdUnitPosition(adUnitCode) { } function calculateVisibility(rect) { - const windowHeight = window.innerHeight; - const windowWidth = window.innerWidth; + const { innerWidth: windowWidth, innerHeight: windowHeight } = getWinDimensions(); // Element is not in viewport if (rect.bottom < 0 || rect.right < 0 || rect.top > windowHeight || rect.left > windowWidth) { @@ -186,12 +187,13 @@ const converter = ortbConverter({ } }); + const { innerWidth: windowWidth, innerHeight: windowHeight } = getWinDimensions(); deepSetValue(request, 'site.ext.data.valuad_rtd', { pageviewId: _VALUAD.pageviewId, session: session, features: { page_dimensions: `${document.documentElement.scrollWidth}x${document.documentElement.scrollHeight}`, - viewport_dimensions: `${window.innerWidth}x${window.innerHeight}`, + viewport_dimensions: `${windowWidth}x${windowHeight}`, user_timestamp: Math.floor(Date.now() / 1000), dom_loading: window.performance?.timing?.domContentLoadedEventEnd - window.performance?.timing?.navigationStart } From 8ac1889b72cbf8b5a960fa0168f347501512cb03 Mon Sep 17 00:00:00 2001 From: natanavra Date: Tue, 22 Apr 2025 13:57:29 +0300 Subject: [PATCH 34/44] Remove irrelevant tests --- test/spec/modules/valuadBidAdapter_spec.js | 89 +--------------------- 1 file changed, 1 insertion(+), 88 deletions(-) diff --git a/test/spec/modules/valuadBidAdapter_spec.js b/test/spec/modules/valuadBidAdapter_spec.js index 8c4bcd791b8..4eb06a91895 100644 --- a/test/spec/modules/valuadBidAdapter_spec.js +++ b/test/spec/modules/valuadBidAdapter_spec.js @@ -2,7 +2,7 @@ import { expect, util } from 'chai'; import * as sinon from 'sinon'; import { spec, _VALUAD } from 'modules/valuadBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import { BANNER, VIDEO, NATIVE } from 'src/mediaTypes.js'; +import { BANNER } from 'src/mediaTypes.js'; import { deepClone, generateUUID } from 'src/utils.js'; import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; @@ -104,27 +104,6 @@ describe('ValuadAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return true for a valid video bid request', function () { - let videoBid = deepClone(bid); - videoBid.mediaTypes = { - [VIDEO]: { - playerSize: [[640, 480]] - } - }; - expect(spec.isBidRequestValid(videoBid)).to.equal(true); - }); - - it('should return true for a valid native bid request', function () { - let nativeBid = deepClone(bid); - nativeBid.mediaTypes = { - [NATIVE]: { - title: { required: true, len: 140 }, - image: { required: true, sizes: [300, 250] }, - } - }; - expect(spec.isBidRequestValid(nativeBid)).to.equal(true); - }); - it('should return false when placementId is missing', function () { let invalidBid = deepClone(bid); delete invalidBid.params.placementId; @@ -154,14 +133,6 @@ describe('ValuadAdapter', function () { delete invalidBid.mediaTypes[BANNER].sizes; expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); - - it('should return false when video playerSize is missing', function () { - let invalidBid = deepClone(bid); - invalidBid.mediaTypes = { - [VIDEO]: {} - }; - expect(spec.isBidRequestValid(invalidBid)).to.equal(false); - }); }); describe('buildRequests', function () { @@ -307,64 +278,6 @@ describe('ValuadAdapter', function () { expect(payload.imp[0].bidfloorcur).to.equal('USD'); sinon.assert.calledWith(bidWithFloor[0].getFloor, { currency: 'USD', mediaType: BANNER, size: [300, 250] }); }); - - it('should handle video params correctly', function () { - let videoBid = deepClone(validBidRequests[0]); - videoBid.mediaTypes = { - [VIDEO]: { - playerSize: [[640, 480]], - mimes: ['video/mp4'], - protocols: [2, 3], - minduration: 5, - maxduration: 30, - startdelay: 0, - placement: 1, - linearity: 1, - skip: 1, - skipmin: 5, - skipafter: 10, - playbackmethod: [1, 3], - api: [1, 2] - } - }; - let videoReq = deepClone(bidderRequest); - videoReq.bids = [videoBid]; - - const request = spec.buildRequests([videoBid], videoReq); - const payload = request[0].data; - const imp = payload.imp[0]; - - expect(imp.video).to.exist; - expect(imp.video.w).to.equal(640); - expect(imp.video.h).to.equal(480); - expect(imp.video.mimes).to.deep.equal(['video/mp4']); - expect(imp.video.protocols).to.deep.equal([2, 3]); - expect(imp.video.minduration).to.equal(5); - expect(imp.video.maxduration).to.equal(30); - }); - - it('should handle native params correctly', function () { - let nativeBid = deepClone(validBidRequests[0]); - nativeBid.mediaTypes = { - [NATIVE]: { - title: { required: true, len: 100 }, - image: { required: true, sizes: [1200, 627], mimes: ['image/jpeg'] }, - } - }; - let nativeReq = deepClone(bidderRequest); - nativeReq.bids = [nativeBid]; - - const request = spec.buildRequests([nativeBid], nativeReq); - const payload = request[0].data; - const imp = payload.imp[0]; - - expect(imp.native).to.exist; - expect(imp.native.ver).to.equal('1.2'); - expect(imp.native.assets).to.be.an('array').with.lengthOf(2); - expect(imp.native.assets[0].title.len).to.equal(100); - expect(imp.native.assets[1].img.w).to.equal(1200); - expect(imp.native.assets[1].img.h).to.equal(627); - }); }); describe('interpretResponse', function () { From 269560df44417a58257d41cebd17f1edd370d9c4 Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Wed, 7 May 2025 14:07:32 +0300 Subject: [PATCH 35/44] Fixed tests to run without using global --- test/spec/modules/valuadBidAdapter_spec.js | 167 +++++++++++++-------- 1 file changed, 107 insertions(+), 60 deletions(-) diff --git a/test/spec/modules/valuadBidAdapter_spec.js b/test/spec/modules/valuadBidAdapter_spec.js index 4eb06a91895..7e92e9066c2 100644 --- a/test/spec/modules/valuadBidAdapter_spec.js +++ b/test/spec/modules/valuadBidAdapter_spec.js @@ -18,7 +18,8 @@ describe('ValuadAdapter', function () { let requestToServer; let validBidRequests; let bidderRequest; - let navigatorStub; + let sandbox; + let clock; before(function() { validBidRequests = [ @@ -70,11 +71,113 @@ describe('ValuadAdapter', function () { ext: { data: { pageType: 'article' } } + }, + device: { + w: 1920, + h: 1080, + language: 'en-US' } } }; + }); + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + clock = sinon.useFakeTimers(); + + // Stub performance timing + const performanceStub = { + timing: { + navigationStart: 100, + domContentLoadedEventEnd: 1000 + } + }; + Object.defineProperty(window, 'performance', { + value: performanceStub, + configurable: true, + writable: true + }); + + // Stub navigator + const navigatorStub = { + userAgent: 'Test User Agent', + language: 'en-US' + }; + Object.defineProperty(window, 'navigator', { + value: navigatorStub, + configurable: true, + writable: true + }); + + // Stub utility functions + sandbox.stub(utils, 'getWindowTop').returns({ + location: { href: 'http://test.com/page' }, + document: { + referrer: 'http://referrer.com', + documentElement: { + clientWidth: 1200, + scrollHeight: 2000, + scrollWidth: 1200 + } + }, + innerWidth: 1200, + innerHeight: 800, + screen: { width: 1920, height: 1080 }, + pageXOffset: 0, + pageYOffset: 0 + }); + + sandbox.stub(utils, 'getWindowSelf').returns({ + location: { href: 'http://test.com/page' }, + document: { + referrer: 'http://referrer.com', + documentElement: { + clientWidth: 1200, + scrollHeight: 2000, + scrollWidth: 1200 + } + }, + innerWidth: 1200, + innerHeight: 800, + screen: { width: 1920, height: 1080 }, + pageXOffset: 0, + pageYOffset: 0 + }); + + sandbox.stub(utils, 'canAccessWindowTop').returns(true); + sandbox.stub(utils, 'getDNT').returns(false); + sandbox.stub(utils, 'generateUUID').returns('test-uuid'); + + sandbox.stub(refererDetection, 'parseDomain').returns('test.com'); + + sandbox.stub(gptUtils, 'getGptSlotInfoForAdUnitCode').returns({ + gptSlot: '/123/adunit', + divId: 'div-gpt-ad-123' + }); + + sandbox.stub(config, 'getConfig').withArgs('coppa').returns(false); + + sandbox.stub(BoundingClientRect, 'getBoundingClientRect').returns({ + left: 10, + top: 20, + right: 310, + bottom: 270, + width: 300, + height: 250 + }); + + _VALUAD.pageviewId = 'test-pageview-id'; + _VALUAD.sessionId = 'test-session-id'; + _VALUAD.sessionStartTime = 1678886400000; + _VALUAD.pageLoadTime = 900; + _VALUAD.userActivity = { lastActivityTime: 1678886405000, pageviewCount: 1 }; + + requestToServer = spec.buildRequests(validBidRequests, bidderRequest)[0]; + }); - navigatorStub = { userAgent: 'Test User Agent', language: 'en-US' }; + afterEach(function () { + sandbox.restore(); + clock.restore(); }); describe('inherited functions', function () { @@ -136,56 +239,6 @@ describe('ValuadAdapter', function () { }); describe('buildRequests', function () { - let sandbox; - let clock; - let getWindowTopStub; - let getWindowSelfStub; - let detectRefererStub; - let getGptSlotInfoStub; - let getConfigStub; - let getBoundingClientRectStub; - let performanceStub; - - beforeEach(function () { - sandbox = sinon.sandbox.create(); - clock = sinon.useFakeTimers(); - - getWindowTopStub = sandbox.stub(utils, 'getWindowTop').returns({ location: { href: 'http://test.com/page' }, document: { referrer: 'http://referrer.com' } }); - getWindowSelfStub = sandbox.stub(utils, 'getWindowSelf').returns({ location: { href: 'http://test.com/page' }, document: { referrer: 'http://referrer.com' }, innerWidth: 1200, innerHeight: 800, screen: { width: 1920, height: 1080 } }); - sandbox.stub(utils, 'canAccessWindowTop').returns(true); - sandbox.stub(utils, 'getDNT').returns(false); - sandbox.stub(utils, 'generateUUID').returns('test-uuid'); - - detectRefererStub = sandbox.stub(refererDetection, 'parseDomain').returns('test.com'); - - getGptSlotInfoStub = sandbox.stub(gptUtils, 'getGptSlotInfoForAdUnitCode').returns({ gptSlot: '/123/adunit' }); - - getConfigStub = sandbox.stub(config, 'getConfig'); - getConfigStub.withArgs('coppa').returns(false); - - getBoundingClientRectStub = sandbox.stub(BoundingClientRect, 'getBoundingClientRect').returns({ left: 10, top: 20, right: 310, bottom: 270, width: 300, height: 250 }); - - performanceStub = { timing: { navigationStart: 100, domContentLoadedEventEnd: 1000 } }; - Object.defineProperty(global, 'performance', { value: performanceStub, configurable: true, writable: true }); - - Object.defineProperty(global, 'navigator', { value: navigatorStub, configurable: true, writable: true }); - - _VALUAD.pageviewId = 'test-pageview-id'; - _VALUAD.sessionId = 'test-session-id'; - _VALUAD.sessionStartTime = 1678886400000; - _VALUAD.pageLoadTime = 900; - _VALUAD.userActivity = { lastActivityTime: 1678886405000, pageviewCount: 1 }; - - requestToServer = spec.buildRequests(validBidRequests, bidderRequest)[0]; - }); - - afterEach(function () { - sandbox.restore(); - clock.restore(); - delete global.performance; - delete global.navigator; - }); - it('should return a valid server request object', function () { expect(requestToServer).to.exist; expect(requestToServer).to.be.an('object'); @@ -213,8 +266,8 @@ describe('ValuadAdapter', function () { expect(payload.site.ext.data.pageType).to.equal('article'); expect(payload.device).to.exist; - expect(payload.device.userAgent).to.equal(navigatorStub.userAgent); - expect(payload.device.language).to.equal(navigatorStub.language); + expect(payload.device.userAgent).to.equal('Test User Agent'); + expect(payload.device.language).to.equal('en-US'); expect(payload.device.dnt).to.equal(0); expect(payload.device.js).to.equal(1); expect(payload.device.w).to.equal(1920); @@ -284,8 +337,6 @@ describe('ValuadAdapter', function () { let serverResponse; beforeEach(function() { - Object.defineProperty(global, 'navigator', { value: navigatorStub, configurable: true, writable: true }); - serverResponse = { body: { id: 'test-response-id', @@ -320,10 +371,6 @@ describe('ValuadAdapter', function () { }; }); - afterEach(function() { - delete global.navigator; - }); - it('should return an array of valid bid responses', function () { expect(requestToServer).to.exist; const bids = spec.interpretResponse(serverResponse, requestToServer); From 54ddb7414e3a2686617e1be662cd0bf0b9b25782 Mon Sep 17 00:00:00 2001 From: natanavra Date: Wed, 7 May 2025 14:27:48 +0300 Subject: [PATCH 36/44] Update tests to not pollute global space --- test/spec/modules/valuadBidAdapter_spec.js | 167 +++++++++++++-------- 1 file changed, 107 insertions(+), 60 deletions(-) diff --git a/test/spec/modules/valuadBidAdapter_spec.js b/test/spec/modules/valuadBidAdapter_spec.js index 4eb06a91895..7e92e9066c2 100644 --- a/test/spec/modules/valuadBidAdapter_spec.js +++ b/test/spec/modules/valuadBidAdapter_spec.js @@ -18,7 +18,8 @@ describe('ValuadAdapter', function () { let requestToServer; let validBidRequests; let bidderRequest; - let navigatorStub; + let sandbox; + let clock; before(function() { validBidRequests = [ @@ -70,11 +71,113 @@ describe('ValuadAdapter', function () { ext: { data: { pageType: 'article' } } + }, + device: { + w: 1920, + h: 1080, + language: 'en-US' } } }; + }); + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + clock = sinon.useFakeTimers(); + + // Stub performance timing + const performanceStub = { + timing: { + navigationStart: 100, + domContentLoadedEventEnd: 1000 + } + }; + Object.defineProperty(window, 'performance', { + value: performanceStub, + configurable: true, + writable: true + }); + + // Stub navigator + const navigatorStub = { + userAgent: 'Test User Agent', + language: 'en-US' + }; + Object.defineProperty(window, 'navigator', { + value: navigatorStub, + configurable: true, + writable: true + }); + + // Stub utility functions + sandbox.stub(utils, 'getWindowTop').returns({ + location: { href: 'http://test.com/page' }, + document: { + referrer: 'http://referrer.com', + documentElement: { + clientWidth: 1200, + scrollHeight: 2000, + scrollWidth: 1200 + } + }, + innerWidth: 1200, + innerHeight: 800, + screen: { width: 1920, height: 1080 }, + pageXOffset: 0, + pageYOffset: 0 + }); + + sandbox.stub(utils, 'getWindowSelf').returns({ + location: { href: 'http://test.com/page' }, + document: { + referrer: 'http://referrer.com', + documentElement: { + clientWidth: 1200, + scrollHeight: 2000, + scrollWidth: 1200 + } + }, + innerWidth: 1200, + innerHeight: 800, + screen: { width: 1920, height: 1080 }, + pageXOffset: 0, + pageYOffset: 0 + }); + + sandbox.stub(utils, 'canAccessWindowTop').returns(true); + sandbox.stub(utils, 'getDNT').returns(false); + sandbox.stub(utils, 'generateUUID').returns('test-uuid'); + + sandbox.stub(refererDetection, 'parseDomain').returns('test.com'); + + sandbox.stub(gptUtils, 'getGptSlotInfoForAdUnitCode').returns({ + gptSlot: '/123/adunit', + divId: 'div-gpt-ad-123' + }); + + sandbox.stub(config, 'getConfig').withArgs('coppa').returns(false); + + sandbox.stub(BoundingClientRect, 'getBoundingClientRect').returns({ + left: 10, + top: 20, + right: 310, + bottom: 270, + width: 300, + height: 250 + }); + + _VALUAD.pageviewId = 'test-pageview-id'; + _VALUAD.sessionId = 'test-session-id'; + _VALUAD.sessionStartTime = 1678886400000; + _VALUAD.pageLoadTime = 900; + _VALUAD.userActivity = { lastActivityTime: 1678886405000, pageviewCount: 1 }; + + requestToServer = spec.buildRequests(validBidRequests, bidderRequest)[0]; + }); - navigatorStub = { userAgent: 'Test User Agent', language: 'en-US' }; + afterEach(function () { + sandbox.restore(); + clock.restore(); }); describe('inherited functions', function () { @@ -136,56 +239,6 @@ describe('ValuadAdapter', function () { }); describe('buildRequests', function () { - let sandbox; - let clock; - let getWindowTopStub; - let getWindowSelfStub; - let detectRefererStub; - let getGptSlotInfoStub; - let getConfigStub; - let getBoundingClientRectStub; - let performanceStub; - - beforeEach(function () { - sandbox = sinon.sandbox.create(); - clock = sinon.useFakeTimers(); - - getWindowTopStub = sandbox.stub(utils, 'getWindowTop').returns({ location: { href: 'http://test.com/page' }, document: { referrer: 'http://referrer.com' } }); - getWindowSelfStub = sandbox.stub(utils, 'getWindowSelf').returns({ location: { href: 'http://test.com/page' }, document: { referrer: 'http://referrer.com' }, innerWidth: 1200, innerHeight: 800, screen: { width: 1920, height: 1080 } }); - sandbox.stub(utils, 'canAccessWindowTop').returns(true); - sandbox.stub(utils, 'getDNT').returns(false); - sandbox.stub(utils, 'generateUUID').returns('test-uuid'); - - detectRefererStub = sandbox.stub(refererDetection, 'parseDomain').returns('test.com'); - - getGptSlotInfoStub = sandbox.stub(gptUtils, 'getGptSlotInfoForAdUnitCode').returns({ gptSlot: '/123/adunit' }); - - getConfigStub = sandbox.stub(config, 'getConfig'); - getConfigStub.withArgs('coppa').returns(false); - - getBoundingClientRectStub = sandbox.stub(BoundingClientRect, 'getBoundingClientRect').returns({ left: 10, top: 20, right: 310, bottom: 270, width: 300, height: 250 }); - - performanceStub = { timing: { navigationStart: 100, domContentLoadedEventEnd: 1000 } }; - Object.defineProperty(global, 'performance', { value: performanceStub, configurable: true, writable: true }); - - Object.defineProperty(global, 'navigator', { value: navigatorStub, configurable: true, writable: true }); - - _VALUAD.pageviewId = 'test-pageview-id'; - _VALUAD.sessionId = 'test-session-id'; - _VALUAD.sessionStartTime = 1678886400000; - _VALUAD.pageLoadTime = 900; - _VALUAD.userActivity = { lastActivityTime: 1678886405000, pageviewCount: 1 }; - - requestToServer = spec.buildRequests(validBidRequests, bidderRequest)[0]; - }); - - afterEach(function () { - sandbox.restore(); - clock.restore(); - delete global.performance; - delete global.navigator; - }); - it('should return a valid server request object', function () { expect(requestToServer).to.exist; expect(requestToServer).to.be.an('object'); @@ -213,8 +266,8 @@ describe('ValuadAdapter', function () { expect(payload.site.ext.data.pageType).to.equal('article'); expect(payload.device).to.exist; - expect(payload.device.userAgent).to.equal(navigatorStub.userAgent); - expect(payload.device.language).to.equal(navigatorStub.language); + expect(payload.device.userAgent).to.equal('Test User Agent'); + expect(payload.device.language).to.equal('en-US'); expect(payload.device.dnt).to.equal(0); expect(payload.device.js).to.equal(1); expect(payload.device.w).to.equal(1920); @@ -284,8 +337,6 @@ describe('ValuadAdapter', function () { let serverResponse; beforeEach(function() { - Object.defineProperty(global, 'navigator', { value: navigatorStub, configurable: true, writable: true }); - serverResponse = { body: { id: 'test-response-id', @@ -320,10 +371,6 @@ describe('ValuadAdapter', function () { }; }); - afterEach(function() { - delete global.navigator; - }); - it('should return an array of valid bid responses', function () { expect(requestToServer).to.exist; const bids = spec.interpretResponse(serverResponse, requestToServer); From f1eb854616f6ecce38474f6b4098a2a5aff3879e Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Thu, 8 May 2025 12:47:00 +0300 Subject: [PATCH 37/44] Removed global window reference and removed un-required test --- test/spec/modules/valuadBidAdapter_spec.js | 28 ---------------------- 1 file changed, 28 deletions(-) diff --git a/test/spec/modules/valuadBidAdapter_spec.js b/test/spec/modules/valuadBidAdapter_spec.js index 7e92e9066c2..f95f9c6fbe4 100644 --- a/test/spec/modules/valuadBidAdapter_spec.js +++ b/test/spec/modules/valuadBidAdapter_spec.js @@ -85,30 +85,6 @@ describe('ValuadAdapter', function () { sandbox = sinon.sandbox.create(); clock = sinon.useFakeTimers(); - // Stub performance timing - const performanceStub = { - timing: { - navigationStart: 100, - domContentLoadedEventEnd: 1000 - } - }; - Object.defineProperty(window, 'performance', { - value: performanceStub, - configurable: true, - writable: true - }); - - // Stub navigator - const navigatorStub = { - userAgent: 'Test User Agent', - language: 'en-US' - }; - Object.defineProperty(window, 'navigator', { - value: navigatorStub, - configurable: true, - writable: true - }); - // Stub utility functions sandbox.stub(utils, 'getWindowTop').returns({ location: { href: 'http://test.com/page' }, @@ -260,13 +236,9 @@ describe('ValuadAdapter', function () { expect(payload.site.page).to.equal(bidderRequest.refererInfo.topmostLocation); expect(payload.site.referrer).to.equal(bidderRequest.refererInfo.ref); expect(payload.site.top).to.equal(true); - expect(payload.site.ext.data.valuad_rtd.pageviewId).to.equal('test-pageview-id'); - expect(payload.site.ext.data.valuad_rtd.session.id).to.equal('test-session-id'); - expect(payload.site.ext.data.valuad_rtd.features.dom_loading).to.equal(900); expect(payload.site.ext.data.pageType).to.equal('article'); expect(payload.device).to.exist; - expect(payload.device.userAgent).to.equal('Test User Agent'); expect(payload.device.language).to.equal('en-US'); expect(payload.device.dnt).to.equal(0); expect(payload.device.js).to.equal(1); From 8b5bc25851d92c49ff01891762aea177ffd0e548 Mon Sep 17 00:00:00 2001 From: natanavra Date: Thu, 8 May 2025 15:10:13 +0300 Subject: [PATCH 38/44] Remove window reference and unused tests --- test/spec/modules/valuadBidAdapter_spec.js | 28 ---------------------- 1 file changed, 28 deletions(-) diff --git a/test/spec/modules/valuadBidAdapter_spec.js b/test/spec/modules/valuadBidAdapter_spec.js index 7e92e9066c2..f95f9c6fbe4 100644 --- a/test/spec/modules/valuadBidAdapter_spec.js +++ b/test/spec/modules/valuadBidAdapter_spec.js @@ -85,30 +85,6 @@ describe('ValuadAdapter', function () { sandbox = sinon.sandbox.create(); clock = sinon.useFakeTimers(); - // Stub performance timing - const performanceStub = { - timing: { - navigationStart: 100, - domContentLoadedEventEnd: 1000 - } - }; - Object.defineProperty(window, 'performance', { - value: performanceStub, - configurable: true, - writable: true - }); - - // Stub navigator - const navigatorStub = { - userAgent: 'Test User Agent', - language: 'en-US' - }; - Object.defineProperty(window, 'navigator', { - value: navigatorStub, - configurable: true, - writable: true - }); - // Stub utility functions sandbox.stub(utils, 'getWindowTop').returns({ location: { href: 'http://test.com/page' }, @@ -260,13 +236,9 @@ describe('ValuadAdapter', function () { expect(payload.site.page).to.equal(bidderRequest.refererInfo.topmostLocation); expect(payload.site.referrer).to.equal(bidderRequest.refererInfo.ref); expect(payload.site.top).to.equal(true); - expect(payload.site.ext.data.valuad_rtd.pageviewId).to.equal('test-pageview-id'); - expect(payload.site.ext.data.valuad_rtd.session.id).to.equal('test-session-id'); - expect(payload.site.ext.data.valuad_rtd.features.dom_loading).to.equal(900); expect(payload.site.ext.data.pageType).to.equal('article'); expect(payload.device).to.exist; - expect(payload.device.userAgent).to.equal('Test User Agent'); expect(payload.device.language).to.equal('en-US'); expect(payload.device.dnt).to.equal(0); expect(payload.device.js).to.equal(1); From 5f0b246a651208540ca535ba5a8aedf2cde50489 Mon Sep 17 00:00:00 2001 From: natanavra Date: Thu, 8 May 2025 15:31:26 +0300 Subject: [PATCH 39/44] Fix test device language check --- test/spec/modules/valuadBidAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/valuadBidAdapter_spec.js b/test/spec/modules/valuadBidAdapter_spec.js index f95f9c6fbe4..9a8f13830bf 100644 --- a/test/spec/modules/valuadBidAdapter_spec.js +++ b/test/spec/modules/valuadBidAdapter_spec.js @@ -239,7 +239,7 @@ describe('ValuadAdapter', function () { expect(payload.site.ext.data.pageType).to.equal('article'); expect(payload.device).to.exist; - expect(payload.device.language).to.equal('en-US'); + expect(payload.device.language).to.include('en'); expect(payload.device.dnt).to.equal(0); expect(payload.device.js).to.equal(1); expect(payload.device.w).to.equal(1920); From 88e600569b7c110255ff676970aa8136428a33f9 Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Wed, 4 Jun 2025 12:47:04 +0300 Subject: [PATCH 40/44] Removed session and pageview enrichment data --- modules/valuadBidAdapter.js | 58 +------------------------------------ 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index 86cda06f586..7d0f3c93bc9 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -5,10 +5,7 @@ import { cleanObj, deepAccess, deepSetValue, - generateUUID, getWindowSelf, - getWindowTop, - canAccessWindowTop, getDNT, logInfo, triggerPixel, @@ -23,22 +20,6 @@ const BIDDER_CODE = 'valuad'; const AD_URL = 'https://rtb.valuad.io/adapter'; const WON_URL = 'https://hb-dot-valuad.appspot.com/adapter/win'; -export const _VALUAD = (function() { - const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); - - w.VALUAD = w.VALUAD || {}; - w.VALUAD.pageviewId = w.VALUAD.pageviewId || generateUUID(); - w.VALUAD.sessionId = w.VALUAD.sessionId || generateUUID(); - w.VALUAD.sessionStartTime = w.VALUAD.sessionStartTime || Date.now(); - w.VALUAD.pageLoadTime = w.VALUAD.pageLoadTime || window.performance?.timing?.domContentLoadedEventEnd - window.performance?.timing?.navigationStart; - w.VALUAD.userActivity = w.VALUAD.userActivity || { - lastActivityTime: Date.now(), - pageviewCount: (w.VALUAD.userActivity?.pageviewCount || 0) + 1 - }; - - return w.VALUAD; -})(); - // Helper functions to enrich data function getDevice() { const language = navigator.language ? 'language' : 'userLanguage'; @@ -84,17 +65,6 @@ function getSite(bidderRequest) { return siteInfo; } -function getSession() { - return { - id: _VALUAD.sessionId, - startTime: _VALUAD.sessionStartTime, - lastActivityTime: _VALUAD.userActivity.lastActivityTime, - pageviewCount: _VALUAD.userActivity.pageviewCount, - pageLoadTime: _VALUAD.pageLoadTime || 0, - new: _VALUAD.userActivity.pageviewCount === 1 - }; -} - // Add detailed ad unit position detection function detectAdUnitPosition(adUnitCode) { const element = document.getElementById(adUnitCode) || document.getElementById(getGptSlotInfoForAdUnitCode(adUnitCode)?.divId); @@ -163,7 +133,6 @@ const converter = ortbConverter({ const request = buildRequest(imps, bidderRequest, context); const device = getDevice(); const site = getSite(bidderRequest); - const session = getSession(); const gdpr = getGdprConsent(bidderRequest); const uspConsent = deepAccess(bidderRequest, 'uspConsent') || ''; @@ -189,8 +158,6 @@ const converter = ortbConverter({ const { innerWidth: windowWidth, innerHeight: windowHeight } = getWinDimensions(); deepSetValue(request, 'site.ext.data.valuad_rtd', { - pageviewId: _VALUAD.pageviewId, - session: session, features: { page_dimensions: `${document.documentElement.scrollWidth}x${document.documentElement.scrollHeight}`, viewport_dimensions: `${windowWidth}x${windowHeight}`, @@ -241,19 +208,13 @@ const converter = ortbConverter({ // Add additional impression data const positionData = detectAdUnitPosition(bid.adUnitCode); if (positionData) { - deepSetValue(imp, 'ext.data.adg_rtd.adunit_position', positionData.position); + deepSetValue(imp, 'ext.data.position', positionData); deepSetValue(imp, 'ext.data.viewability', positionData.viewportVisibility); } // GPT information const gptInfo = getGptSlotInfoForAdUnitCode(bid.adUnitCode); if (gptInfo) { - deepSetValue(imp, 'ext.data.adserver', { - name: 'gam', - adslot: gptInfo.gptSlot - }); - deepSetValue(imp, 'ext.data.pbadslot', gptInfo.gptSlot); - // If not already set, add gpid if (!imp.ext.gpid && gptInfo.gptSlot) { deepSetValue(imp, 'ext.gpid', gptInfo.gptSlot); @@ -327,20 +288,8 @@ function isBidRequestValid(bid = {}) { } function buildRequests(validBidRequests = [], bidderRequest = {}) { - // Add bid-level metadata for our server to use - validBidRequests = validBidRequests.map(req => { - req.valuadMeta = { - pageviewId: _VALUAD.pageviewId, - adUnitPosition: detectAdUnitPosition(req.adUnitCode) - }; - return req; - }); - const data = converter.toORTB({ validBidRequests, bidderRequest }); - // Update session data - _VALUAD.userActivity.lastActivityTime = Date.now(); - return [{ method: 'POST', url: AD_URL, @@ -352,11 +301,6 @@ function interpretResponse(response, request) { // Restore original call, remove logging and safe navigation const bidResponses = converter.fromORTB({response: response.body, request: request.data}).bids; - // Restore original server-side data processing - if (response.body && response.body.ext && response.body.ext.valuad) { - _VALUAD.serverData = response.body.ext.valuad; - } - return bidResponses; } From 5b21493341bb8edd79d44d4f754dcb129b61be8f Mon Sep 17 00:00:00 2001 From: Tal Lavi <66143754+tal-px@users.noreply.github.com> Date: Wed, 4 Jun 2025 13:54:28 +0300 Subject: [PATCH 41/44] Removed redundant enrichment functions --- modules/valuadBidAdapter.js | 71 ++----------------------------------- 1 file changed, 2 insertions(+), 69 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index 7d0f3c93bc9..e35dc149f19 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -20,51 +20,6 @@ const BIDDER_CODE = 'valuad'; const AD_URL = 'https://rtb.valuad.io/adapter'; const WON_URL = 'https://hb-dot-valuad.appspot.com/adapter/win'; -// Helper functions to enrich data -function getDevice() { - const language = navigator.language ? 'language' : 'userLanguage'; - const deviceInfo = { - userAgent: navigator.userAgent, - language: navigator[language], - dnt: getDNT() ? 1 : 0, - js: 1, - geo: {} - }; - - const { innerWidth: windowWidth, innerHeight: windowHeight, screen } = getWinDimensions(); - // Get screen dimensions - if (window.screen) { - deviceInfo.w = screen.width; - deviceInfo.h = screen.height; - } - - // Get viewport dimensions - deviceInfo.ext = { - vpw: windowWidth, - vph: windowHeight - }; - - return deviceInfo; -} - -function getSite(bidderRequest) { - const { refererInfo } = bidderRequest; - const siteInfo = { - domain: parseDomain(refererInfo.topmostLocation) || '', - page: refererInfo.topmostLocation || '', - referrer: refererInfo.ref || getWindowSelf().document.referrer || '', - top: refererInfo.reachedTop - }; - - // Add page metadata if available - const meta = document.querySelector('meta[name="keywords"]'); - if (meta && meta.content) { - siteInfo.keywords = meta.content; - } - - return siteInfo; -} - // Add detailed ad unit position detection function detectAdUnitPosition(adUnitCode) { const element = document.getElementById(adUnitCode) || document.getElementById(getGptSlotInfoForAdUnitCode(adUnitCode)?.divId); @@ -131,8 +86,6 @@ const converter = ortbConverter({ }, request(buildRequest, imps, bidderRequest, context) { const request = buildRequest(imps, bidderRequest, context); - const device = getDevice(); - const site = getSite(bidderRequest); const gdpr = getGdprConsent(bidderRequest); const uspConsent = deepAccess(bidderRequest, 'uspConsent') || ''; @@ -140,10 +93,6 @@ const converter = ortbConverter({ const { gpp, gpp_sid: gppSid } = deepAccess(bidderRequest, 'ortb2.regs', {}); const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); - // Ensure we have required extensions - deepSetValue(request, 'device', {...request.device, ...device}); - deepSetValue(request, 'site', {...request.site, ...site}); - deepSetValue(request, 'regs', { gdpr: gdpr.consentRequired || 0, coppa: coppa, @@ -156,15 +105,8 @@ const converter = ortbConverter({ } }); - const { innerWidth: windowWidth, innerHeight: windowHeight } = getWinDimensions(); - deepSetValue(request, 'site.ext.data.valuad_rtd', { - features: { - page_dimensions: `${document.documentElement.scrollWidth}x${document.documentElement.scrollHeight}`, - viewport_dimensions: `${windowWidth}x${windowHeight}`, - user_timestamp: Math.floor(Date.now() / 1000), - dom_loading: window.performance?.timing?.domContentLoadedEventEnd - window.performance?.timing?.navigationStart - } - }); + deepSetValue(request, 'device.js', 1); + deepSetValue(request, 'device.geo', {}); // Add bid parameters if (bidderRequest && bidderRequest.bids && bidderRequest.bids.length) { @@ -212,15 +154,6 @@ const converter = ortbConverter({ deepSetValue(imp, 'ext.data.viewability', positionData.viewportVisibility); } - // GPT information - const gptInfo = getGptSlotInfoForAdUnitCode(bid.adUnitCode); - if (gptInfo) { - // If not already set, add gpid - if (!imp.ext.gpid && gptInfo.gptSlot) { - deepSetValue(imp, 'ext.gpid', gptInfo.gptSlot); - } - } - // Handle price floors if (typeof bid.getFloor === 'function') { try { From ab209f9fd0c206176c5764e5e63964f869df0f85 Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Thu, 5 Jun 2025 10:59:35 +0300 Subject: [PATCH 42/44] Updated the adapter to use information we already have in the request instead of creating it again. Also removed non essential data such as pageview, session, etc. --- modules/valuadBidAdapter.js | 39 +++++++++++++--------- test/spec/modules/valuadBidAdapter_spec.js | 35 ++----------------- 2 files changed, 25 insertions(+), 49 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index e35dc149f19..89fc22ab804 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -5,23 +5,19 @@ import { cleanObj, deepAccess, deepSetValue, - getWindowSelf, - getDNT, logInfo, triggerPixel, getWinDimensions, } from '../src/utils.js'; import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; import { config } from '../src/config.js'; -import { parseDomain } from '../src/refererDetection.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; const BIDDER_CODE = 'valuad'; const AD_URL = 'https://rtb.valuad.io/adapter'; const WON_URL = 'https://hb-dot-valuad.appspot.com/adapter/win'; -// Add detailed ad unit position detection -function detectAdUnitPosition(adUnitCode) { +function detectAdUnitPosition(adUnitCode, adSize) { const element = document.getElementById(adUnitCode) || document.getElementById(getGptSlotInfoForAdUnitCode(adUnitCode)?.divId); if (!element) return null; @@ -31,17 +27,14 @@ function detectAdUnitPosition(adUnitCode) { const pageHeight = docElement.scrollHeight; return { - x: Math.round(rect.left + window.pageXOffset), - y: Math.round(rect.top + window.pageYOffset), - w: Math.round(rect.width), - h: Math.round(rect.height), + unitSize: `${rect.width}x${rect.height}`, position: `${Math.round(rect.left + window.pageXOffset)}x${Math.round(rect.top + window.pageYOffset)}`, - viewportVisibility: calculateVisibility(rect), - pageSize: `${pageWidth}x${pageHeight}` + viewportVisibility: calculateVisibility(rect, adSize), + pageSize: `${pageWidth}x${pageHeight}`, }; } -function calculateVisibility(rect) { +function calculateVisibility(rect, adSize) { const { innerWidth: windowWidth, innerHeight: windowHeight } = getWinDimensions(); // Element is not in viewport @@ -50,8 +43,14 @@ function calculateVisibility(rect) { } // Calculate visible area - const visibleHeight = Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0); - const visibleWidth = Math.min(rect.right, windowWidth) - Math.max(rect.left, 0); + let visibleHeight = Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0); + let visibleWidth = Math.min(rect.right, windowWidth) - Math.max(rect.left, 0); + + if (visibleHeight == 0 && visibleWidth == 0) { + visibleHeight = Math.min(rect.top + adSize[1], windowHeight) - Math.max(rect.top, 0); + visibleWidth = Math.min(rect.left + adSize[0], windowWidth) - Math.max(rect.left, 0); + } + const visibleArea = visibleHeight * visibleWidth; const totalArea = rect.height * rect.width; @@ -147,8 +146,17 @@ const converter = ortbConverter({ imp(buildImp, bid, context) { const imp = buildImp(bid, context); + const mediaType = Object.keys(bid.mediaTypes)[0]; + let adSize; + + if (mediaType === BANNER) { + adSize = bid.mediaTypes.banner.sizes && bid.mediaTypes.banner.sizes[0]; + } + + if (!adSize) { adSize = [0, 0]; } + // Add additional impression data - const positionData = detectAdUnitPosition(bid.adUnitCode); + const positionData = detectAdUnitPosition(bid.adUnitCode, adSize); if (positionData) { deepSetValue(imp, 'ext.data.position', positionData); deepSetValue(imp, 'ext.data.viewability', positionData.viewportVisibility); @@ -157,7 +165,6 @@ const converter = ortbConverter({ // Handle price floors if (typeof bid.getFloor === 'function') { try { - const mediaType = Object.keys(bid.mediaTypes)[0]; let size; if (mediaType === BANNER) { diff --git a/test/spec/modules/valuadBidAdapter_spec.js b/test/spec/modules/valuadBidAdapter_spec.js index f95f9c6fbe4..67bac0e90a9 100644 --- a/test/spec/modules/valuadBidAdapter_spec.js +++ b/test/spec/modules/valuadBidAdapter_spec.js @@ -1,6 +1,6 @@ -import { expect, util } from 'chai'; +import { expect } from 'chai'; import * as sinon from 'sinon'; -import { spec, _VALUAD } from 'modules/valuadBidAdapter.js'; +import { spec } from 'modules/valuadBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { BANNER } from 'src/mediaTypes.js'; import { deepClone, generateUUID } from 'src/utils.js'; @@ -142,12 +142,6 @@ describe('ValuadAdapter', function () { height: 250 }); - _VALUAD.pageviewId = 'test-pageview-id'; - _VALUAD.sessionId = 'test-session-id'; - _VALUAD.sessionStartTime = 1678886400000; - _VALUAD.pageLoadTime = 900; - _VALUAD.userActivity = { lastActivityTime: 1678886405000, pageviewCount: 1 }; - requestToServer = spec.buildRequests(validBidRequests, bidderRequest)[0]; }); @@ -232,20 +226,13 @@ describe('ValuadAdapter', function () { expect(payload.tmax).to.equal(bidderRequest.timeout); expect(payload.site).to.exist; - expect(payload.site.domain).to.equal('test.com'); - expect(payload.site.page).to.equal(bidderRequest.refererInfo.topmostLocation); - expect(payload.site.referrer).to.equal(bidderRequest.refererInfo.ref); - expect(payload.site.top).to.equal(true); expect(payload.site.ext.data.pageType).to.equal('article'); expect(payload.device).to.exist; expect(payload.device.language).to.equal('en-US'); - expect(payload.device.dnt).to.equal(0); expect(payload.device.js).to.equal(1); expect(payload.device.w).to.equal(1920); expect(payload.device.h).to.equal(1080); - expect(payload.device.ext.vpw).to.be.a('number'); - expect(payload.device.ext.vph).to.be.a('number'); expect(payload.regs).to.exist; expect(payload.regs.gdpr).to.equal(1); @@ -263,10 +250,6 @@ describe('ValuadAdapter', function () { expect(imp.banner).to.exist; expect(imp.banner.format).to.be.an('array').with.lengthOf(2); expect(imp.banner.format[0]).to.deep.equal({ w: 300, h: 250 }); - expect(imp.ext.data.adserver.name).to.equal('gam'); - expect(imp.ext.data.adserver.adslot).to.equal('/123/adunit'); - expect(imp.ext.data.pbadslot).to.equal('/123/adunit'); - expect(imp.ext.gpid).to.equal('/123/adunit'); }); it('should include schain if present', function () { @@ -383,20 +366,6 @@ describe('ValuadAdapter', function () { const fn = () => spec.interpretResponse(responseNoBody, requestToServer); expect(fn).to.throw(); }); - - it('should populate _VALUAD.serverData if ext.valuad exists in response', function () { - _VALUAD.serverData = undefined; - spec.interpretResponse(serverResponse, requestToServer); - expect(_VALUAD.serverData).to.deep.equal({ serverInfo: 'some data' }); - }); - - it('should not change _VALUAD.serverData if ext.valuad is missing', function () { - _VALUAD.serverData = { initial: 'value' }; - let responseNoExt = deepClone(serverResponse); - delete responseNoExt.body.ext; - spec.interpretResponse(responseNoExt, requestToServer); - expect(_VALUAD.serverData).to.deep.equal({ initial: 'value' }); - }); }); describe('getUserSyncs', function () { From f45d486f0ee58f273ca1ca81a9e9d80280f62e8c Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:45:15 +0300 Subject: [PATCH 43/44] Removed unnecessary viewabilty functions and used the percentInView library --- modules/valuadBidAdapter.js | 68 +++++++++++++------------------------ 1 file changed, 23 insertions(+), 45 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index 89fc22ab804..bb993b5e098 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -7,60 +7,32 @@ import { deepSetValue, logInfo, triggerPixel, - getWinDimensions, + getWindowSelf, + getWindowTop } from '../src/utils.js'; import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; import { config } from '../src/config.js'; -import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; +import { getBoundingBox, percentInView } from '../libraries/percentInView/percentInView.js'; const BIDDER_CODE = 'valuad'; const AD_URL = 'https://rtb.valuad.io/adapter'; const WON_URL = 'https://hb-dot-valuad.appspot.com/adapter/win'; -function detectAdUnitPosition(adUnitCode, adSize) { - const element = document.getElementById(adUnitCode) || document.getElementById(getGptSlotInfoForAdUnitCode(adUnitCode)?.divId); - if (!element) return null; - - const rect = getBoundingClientRect(element); - const docElement = document.documentElement; - const pageWidth = docElement.clientWidth; - const pageHeight = docElement.scrollHeight; - - return { - unitSize: `${rect.width}x${rect.height}`, - position: `${Math.round(rect.left + window.pageXOffset)}x${Math.round(rect.top + window.pageYOffset)}`, - viewportVisibility: calculateVisibility(rect, adSize), - pageSize: `${pageWidth}x${pageHeight}`, - }; -} - -function calculateVisibility(rect, adSize) { - const { innerWidth: windowWidth, innerHeight: windowHeight } = getWinDimensions(); - - // Element is not in viewport - if (rect.bottom < 0 || rect.right < 0 || rect.top > windowHeight || rect.left > windowWidth) { - return 0; +function _isIframe() { + try { + return getWindowSelf() !== getWindowTop(); + } catch (e) { + return true; } - - // Calculate visible area - let visibleHeight = Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0); - let visibleWidth = Math.min(rect.right, windowWidth) - Math.max(rect.left, 0); - - if (visibleHeight == 0 && visibleWidth == 0) { - visibleHeight = Math.min(rect.top + adSize[1], windowHeight) - Math.max(rect.top, 0); - visibleWidth = Math.min(rect.left + adSize[0], windowWidth) - Math.max(rect.left, 0); - } - - const visibleArea = visibleHeight * visibleWidth; - const totalArea = rect.height * rect.width; - - return totalArea > 0 ? visibleArea / totalArea : 0; } function getGdprConsent(bidderRequest) { if (!deepAccess(bidderRequest, 'gdprConsent')) { return false; } +function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; +} const { apiVersion, @@ -75,6 +47,8 @@ function getGdprConsent(bidderRequest) { consentRequired: gdprApplies ? 1 : 0, allowAuctionWithoutConsent: allowAuctionWithoutConsent ? 1 : 0 }); +function _getViewability(element, topWin, { w, h } = {}) { + return topWin.document.visibilityState === 'visible' ? percentInView(element, { w, h }) : 0; } // Enhanced ORTBConverter with additional data @@ -155,12 +129,16 @@ const converter = ortbConverter({ if (!adSize) { adSize = [0, 0]; } - // Add additional impression data - const positionData = detectAdUnitPosition(bid.adUnitCode, adSize); - if (positionData) { - deepSetValue(imp, 'ext.data.position', positionData); - deepSetValue(imp, 'ext.data.viewability', positionData.viewportVisibility); - } + const size = {w: adSize[0], h: adSize[1]}; + + const element = document.getElementById(bid.adUnitCode) || document.getElementById(getGptSlotInfoForAdUnitCode(bid.adUnitCode)?.divId); + const viewabilityAmount = _isViewabilityMeasurable(element) ? _getViewability(element, getWindowTop(), size) : 0; + + const rect = getBoundingBox(element, size); + const position = `${Math.round(rect.left + window.pageXOffset)}x${Math.round(rect.top + window.pageYOffset)}`; + + deepSetValue(imp, 'ext.data.viewability', viewabilityAmount); + deepSetValue(imp, 'ext.data.position', position); // Handle price floors if (typeof bid.getFloor === 'function') { From fd26621150c4ff0a93690ed5de9e12f5d5226ce1 Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:46:59 +0300 Subject: [PATCH 44/44] Removed redundant reassembling of the gdprConsent object --- modules/valuadBidAdapter.js | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index bb993b5e098..0ab028ba5c1 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -2,7 +2,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { - cleanObj, deepAccess, deepSetValue, logInfo, @@ -26,27 +25,10 @@ function _isIframe() { } } -function getGdprConsent(bidderRequest) { - if (!deepAccess(bidderRequest, 'gdprConsent')) { - return false; - } function _isViewabilityMeasurable(element) { return !_isIframe() && element !== null; } - const { - apiVersion, - gdprApplies, - consentString, - allowAuctionWithoutConsent - } = bidderRequest.gdprConsent; - - return cleanObj({ - apiVersion, - consentString, - consentRequired: gdprApplies ? 1 : 0, - allowAuctionWithoutConsent: allowAuctionWithoutConsent ? 1 : 0 - }); function _getViewability(element, topWin, { w, h } = {}) { return topWin.document.visibilityState === 'visible' ? percentInView(element, { w, h }) : 0; } @@ -60,14 +42,14 @@ const converter = ortbConverter({ request(buildRequest, imps, bidderRequest, context) { const request = buildRequest(imps, bidderRequest, context); - const gdpr = getGdprConsent(bidderRequest); + const gdpr = deepAccess(bidderRequest, 'gdprConsent') || {}; const uspConsent = deepAccess(bidderRequest, 'uspConsent') || ''; const coppa = config.getConfig('coppa') === true ? 1 : 0; const { gpp, gpp_sid: gppSid } = deepAccess(bidderRequest, 'ortb2.regs', {}); const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); deepSetValue(request, 'regs', { - gdpr: gdpr.consentRequired || 0, + gdpr: gdpr.gdprApplies ? 1 : 0, coppa: coppa, us_privacy: uspConsent, ext: {