From 521b06b5f3fffe3536f4469a7a11837a7e60a8a0 Mon Sep 17 00:00:00 2001 From: dmytro-po Date: Thu, 25 Sep 2025 14:28:06 +0300 Subject: [PATCH 1/7] AGT685: Remove device info logic (#42) --- .../intentIqConstants/intentIqConstants.js | 9 ------ modules/intentIqIdSystem.js | 31 ++----------------- 2 files changed, 2 insertions(+), 38 deletions(-) diff --git a/libraries/intentIqConstants/intentIqConstants.js b/libraries/intentIqConstants/intentIqConstants.js index 945bcfad4ad..049470e5cac 100644 --- a/libraries/intentIqConstants/intentIqConstants.js +++ b/libraries/intentIqConstants/intentIqConstants.js @@ -15,15 +15,6 @@ export const HOURS_24 = 86400000; export const INVALID_ID = 'INVALID_ID'; -export const SCREEN_PARAMS = { - 0: 'windowInnerHeight', - 1: 'windowInnerWidth', - 2: 'devicePixelRatio', - 3: 'windowScreenHeight', - 4: 'windowScreenWidth', - 5: 'language' -}; - export const SYNC_REFRESH_MILL = 3600000; export const META_DATA_CONSTANT = 256; diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index df26b3ce2b3..f9372823d6a 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -5,7 +5,7 @@ * @requires module:modules/userId */ -import {logError, isPlainObject, isStr, isNumber, getWinDimensions} from '../src/utils.js'; +import {logError, isPlainObject, isStr, isNumber} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js' import {detectBrowser} from '../libraries/intentIqUtils/detectBrowserUtils.js'; @@ -21,7 +21,7 @@ import { CLIENT_HINTS_KEY, EMPTY, GVLID, - VERSION, INVALID_ID, SCREEN_PARAMS, SYNC_REFRESH_MILL, META_DATA_CONSTANT, PREBID, + VERSION, INVALID_ID, SYNC_REFRESH_MILL, META_DATA_CONSTANT, PREBID, HOURS_24, CH_KEYS } from '../libraries/intentIqConstants/intentIqConstants.js'; import {SYNC_KEY} from '../libraries/intentIqUtils/getSyncKey.js'; @@ -73,36 +73,11 @@ function generateGUID() { return guid; } -function collectDeviceInfo() { - const windowDimensions = getWinDimensions(); - return { - windowInnerHeight: windowDimensions.innerHeight, - windowInnerWidth: windowDimensions.innerWidth, - devicePixelRatio: windowDimensions.devicePixelRatio, - windowScreenHeight: windowDimensions.screen.height, - windowScreenWidth: windowDimensions.screen.width, - language: navigator.language - } -} - function addUniquenessToUrl(url) { url += '&tsrnd=' + Math.floor(Math.random() * 1000) + '_' + new Date().getTime(); return url; } -function appendDeviceInfoToUrl(url, deviceInfo) { - const screenParamsString = Object.entries(SCREEN_PARAMS) - .map(([index, param]) => { - const value = (deviceInfo)[param]; - return `${index}:${value}`; - }) - .join(','); - - url += `&cz=${encodeURIComponent(screenParamsString)}`; - url += `&dw=${deviceInfo.windowScreenWidth}&dh=${deviceInfo.windowScreenHeight}&dpr=${deviceInfo.devicePixelRatio}&lan=${deviceInfo.language}`; - return url; -} - function appendFirstPartyData (url, firstPartyData, partnerData) { url += firstPartyData.pid ? '&pid=' + encodeURIComponent(firstPartyData.pid) : ''; url += firstPartyData.pcid ? '&iiqidtype=2&iiqpcid=' + encodeURIComponent(firstPartyData.pcid) : ''; @@ -169,7 +144,6 @@ function addMetaData(url, data) { } export function createPixelUrl(firstPartyData, clientHints, configParams, partnerData, cmpData) { - const deviceInfo = collectDeviceInfo(); const browser = detectBrowser(); let url = iiqPixelServerAddress(configParams, cmpData.gdprString); @@ -179,7 +153,6 @@ export function createPixelUrl(firstPartyData, clientHints, configParams, partne url = appendPartnersFirstParty(url, configParams); url = addUniquenessToUrl(url); url += partnerData?.clientType ? '&idtype=' + partnerData.clientType : ''; - if (deviceInfo) url = appendDeviceInfoToUrl(url, deviceInfo); url += VERSION ? '&jsver=' + VERSION : ''; if (clientHints) url += '&uh=' + encodeURIComponent(clientHints); url = appendVrrefAndFui(url, configParams.domainName); From a4df7d7d2b74e03f93dcc1d8eeda5b7ad6d78a13 Mon Sep 17 00:00:00 2001 From: dmytro-po Date: Mon, 29 Sep 2025 10:29:53 +0300 Subject: [PATCH 2/7] AGT-680: Don't send gam in browserBlacklist (#44) * AGT-680: Dont send gam in browserBlacklist * AGT-680: fix commas --- modules/intentIqIdSystem.js | 16 +++++---- test/spec/modules/intentIqIdSystem_spec.js | 40 ++++++++++++++++++++++ 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index f9372823d6a..3332455d3cd 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -192,7 +192,8 @@ function sendSyncRequest(allowedStorage, url, partner, firstPartyData, newUser) * @param {string} gamParameterName - The name of the GAM targeting parameter where the group value will be stored. * @param {string} userGroup - The A/B testing group assigned to the user (e.g., 'A', 'B', or a custom value). */ -export function setGamReporting(gamObjectReference, gamParameterName, userGroup) { +export function setGamReporting(gamObjectReference, gamParameterName, userGroup, isBlacklisted = false) { + if (isBlacklisted) return; if (isPlainObject(gamObjectReference) && gamObjectReference.cmd) { gamObjectReference.cmd.push(() => { gamObjectReference @@ -323,7 +324,12 @@ export const intentIqIdSubmodule = { const gdprDetected = cmpData.gdprString; firstPartyData = tryParse(readData(FIRST_PARTY_KEY_FINAL, allowedStorage)); const isGroupB = firstPartyData?.group === WITHOUT_IIQ; - setGamReporting(gamObjectReference, gamParameterName, firstPartyData?.group); + const currentBrowserLowerCase = detectBrowser(); + const browserBlackList = typeof configParams.browserBlackList === 'string' ? configParams.browserBlackList.toLowerCase() : ''; + const isBlacklisted = browserBlackList?.includes(currentBrowserLowerCase); + let newUser = false; + + setGamReporting(gamObjectReference, gamParameterName, firstPartyData?.group, isBlacklisted); if (groupChanged) groupChanged(firstPartyData?.group || NOT_YET_DEFINED); @@ -332,10 +338,6 @@ export const intentIqIdSubmodule = { }, configParams.timeoutInMillis || 500 ); - const currentBrowserLowerCase = detectBrowser(); - const browserBlackList = typeof configParams.browserBlackList === 'string' ? configParams.browserBlackList.toLowerCase() : ''; - let newUser = false; - if (!firstPartyData?.pcid) { const firstPartyId = generateGUID(); firstPartyData = { @@ -451,7 +453,7 @@ export const intentIqIdSubmodule = { } // Check if current browser is in blacklist - if (browserBlackList?.includes(currentBrowserLowerCase)) { + if (isBlacklisted) { logError('User ID - intentIqId submodule: browser is in blacklist! Data will be not provided.'); if (configParams.callback) configParams.callback(''); diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index 00ca52bf80a..42cff5c2582 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -393,6 +393,46 @@ describe('IntentIQ tests', function () { expect(targetingKeys).to.include(customParamName); }); + it('should NOT call GAM setTargeting when current browser is in browserBlackList', function () { + const usedBrowser = 'chrome'; + const gam = mockGAM(); + const pa = gam.pubads(); + sinon.stub(gam, 'pubads').returns(pa); + + const originalSetTargeting = pa.setTargeting; + let setTargetingCalls = 0; + pa.setTargeting = function (...args) { + setTargetingCalls++; + return originalSetTargeting.apply(this, args); + }; + + localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify({ + pcid: 'pcid-1', + pcidDate: Date.now(), + group: 'A', + isOptedOut: false, + date: Date.now(), + sCal: Date.now() + })); + + const cfg = { + params: { + partner, + gamObjectReference: gam, + gamParameterName: 'custom_gam_param', + browserBlackList: usedBrowser + } + }; + + intentIqIdSubmodule.getId(cfg); + gam.cmd.forEach(fn => fn()); + const currentBrowserLowerCase = detectBrowser(); + if (currentBrowserLowerCase === usedBrowser) { + expect(setTargetingCalls).to.equal(0); + expect(pa.getTargetingKeys()).to.not.include('custom_gam_param'); + } + }); + it('should not throw Uncaught TypeError when IntentIQ endpoint returns empty response', async function () { const callBackSpy = sinon.spy(); const submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; From e8031c15880fb5ee430ee34791eb29e605930de7 Mon Sep 17 00:00:00 2001 From: DimaIntentIQ <139111483+DimaIntentIQ@users.noreply.github.com> Date: Thu, 2 Oct 2025 20:25:52 +0300 Subject: [PATCH 3/7] Agt 682 implementation for module (#45) * add GAM prediction * Update version number, add tests, update documentation * Fix property name * refactor export file * fix import typo --- .../intentIqConstants/intentIqConstants.js | 2 +- .../intentIqUtils/gamPredictionReport.js | 97 +++++++ modules/intentIqAnalyticsAdapter.js | 274 ++++++++++++------ modules/intentIqIdSystem.js | 3 + modules/intentIqIdSystem.md | 5 +- .../modules/intentIqAnalyticsAdapter_spec.js | 145 +++++++-- 6 files changed, 416 insertions(+), 110 deletions(-) create mode 100644 libraries/intentIqUtils/gamPredictionReport.js diff --git a/libraries/intentIqConstants/intentIqConstants.js b/libraries/intentIqConstants/intentIqConstants.js index 049470e5cac..0992f4c26b3 100644 --- a/libraries/intentIqConstants/intentIqConstants.js +++ b/libraries/intentIqConstants/intentIqConstants.js @@ -9,7 +9,7 @@ export const BLACK_LIST = 'L'; export const CLIENT_HINTS_KEY = '_iiq_ch'; export const EMPTY = 'EMPTY'; export const GVLID = '1323'; -export const VERSION = 0.31; +export const VERSION = 0.32; export const PREBID = 'pbjs'; export const HOURS_24 = 86400000; diff --git a/libraries/intentIqUtils/gamPredictionReport.js b/libraries/intentIqUtils/gamPredictionReport.js new file mode 100644 index 00000000000..09db065f494 --- /dev/null +++ b/libraries/intentIqUtils/gamPredictionReport.js @@ -0,0 +1,97 @@ +import { getEvents } from '../../src/events.js'; +import { logError } from '../../src/utils.js'; + +export function gamPredictionReport (gamObjectReference, sendData) { + try { + if (!gamObjectReference || !sendData) logError('Failed to get gamPredictionReport, required data is missed'); + const getSlotTargeting = (slot) => { + const kvs = {}; + try { + (slot.getTargetingKeys() || []).forEach((k) => { + kvs[k] = slot.getTargeting(k); + }); + } catch (e) { + logError('Failed to get targeting keys: ' + e); + } + return kvs; + }; + + const extractWinData = (gamEvent) => { + const slot = gamEvent.slot; + const targeting = getSlotTargeting(slot); + + const dataToSend = { + placementId: slot.getSlotElementId && slot.getSlotElementId(), + adUnitPath: slot.getAdUnitPath && slot.getAdUnitPath(), + bidderCode: targeting.hb_bidder ? targeting.hb_bidder[0] : null, + biddingPlatformId: 5 + }; + + if (dataToSend.placementId) { + // TODO check auto subscription to prebid events + const bidWonEvents = getEvents().filter((ev) => ev.eventType === 'bidWon'); + if (bidWonEvents.length) { + for (let i = bidWonEvents.length - 1; i >= 0; i--) { + const element = bidWonEvents[i]; + if ( + dataToSend.placementId === element.id && + targeting.hb_adid && + targeting.hb_adid[0] === element.args.adId + ) { + return; // don't send report if there was bidWon event earlier + } + } + } + const endEvents = getEvents().filter((ev) => ev.eventType === 'auctionEnd'); + if (endEvents.length) { + for (let i = endEvents.length - 1; i >= 0; i--) { + // starting from the last event + const element = endEvents[i]; + if (element.args?.adUnitCodes?.includes(dataToSend.placementId)) { + const defineRelevantData = (bid) => { + dataToSend.cpm = bid.cpm + 0.01; // add one cent to the cpm + dataToSend.currency = bid.currency; + dataToSend.originalCpm = bid.originalCpm; + dataToSend.originalCurrency = bid.originalCurrency; + dataToSend.status = bid.status; + dataToSend.prebidAuctionId = element.args?.auctionId; + }; + if (dataToSend.bidderCode) { + const relevantBid = element.args?.bidsReceived.find( + (item) => + item.bidder === dataToSend.bidderCode && + item.adUnitCode === dataToSend.placementId + ); + if (relevantBid) { + defineRelevantData(relevantBid); + break; + } + } else { + let highestBid = 0; + element.args?.bidsReceived.forEach((bid) => { + if (bid.adUnitCode === dataToSend.placementId && bid.cpm > highestBid) { + highestBid = bid.cpm; + defineRelevantData(bid); + } + }); + break; + } + } + } + } + } + return dataToSend; + }; + gamObjectReference.cmd.push(() => { + gamObjectReference.pubads().addEventListener('slotRenderEnded', (event) => { + if (event.isEmpty) return; + const data = extractWinData(event); + if (data?.cpm) { + sendData(data); + } + }); + }); + } catch (error) { + this.logger.error('Failed to subscribe to GAM: ' + error); + } +}; diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js index f1322d8a982..e4caaaf1768 100644 --- a/modules/intentIqAnalyticsAdapter.js +++ b/modules/intentIqAnalyticsAdapter.js @@ -1,26 +1,39 @@ -import {logError, logInfo} from '../src/utils.js'; +import { isPlainObject, logError, logInfo } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import {ajax} from '../src/ajax.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {config} from '../src/config.js'; -import {EVENTS} from '../src/constants.js'; -import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; -import {detectBrowser} from '../libraries/intentIqUtils/detectBrowserUtils.js'; -import {appendSPData} from '../libraries/intentIqUtils/urlUtils.js'; -import {appendVrrefAndFui, getReferrer} from '../libraries/intentIqUtils/getRefferer.js'; -import {getCmpData} from '../libraries/intentIqUtils/getCmpData.js' -import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY, VERSION, PREBID} from '../libraries/intentIqConstants/intentIqConstants.js'; -import {readData, defineStorageType} from '../libraries/intentIqUtils/storageUtils.js'; -import {reportingServerAddress} from '../libraries/intentIqUtils/intentIqConfig.js'; +import { ajax } from '../src/ajax.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { config } from '../src/config.js'; +import { EVENTS } from '../src/constants.js'; +import { MODULE_TYPE_ANALYTICS } from '../src/activities/modules.js'; +import { detectBrowser } from '../libraries/intentIqUtils/detectBrowserUtils.js'; +import { appendSPData } from '../libraries/intentIqUtils/urlUtils.js'; +import { appendVrrefAndFui, getReferrer } from '../libraries/intentIqUtils/getRefferer.js'; +import { getCmpData } from '../libraries/intentIqUtils/getCmpData.js'; +import { + CLIENT_HINTS_KEY, + FIRST_PARTY_KEY, + VERSION, + PREBID +} from '../libraries/intentIqConstants/intentIqConstants.js'; +import { readData, defineStorageType } from '../libraries/intentIqUtils/storageUtils.js'; +import { reportingServerAddress } from '../libraries/intentIqUtils/intentIqConfig.js'; import { handleAdditionalParams } from '../libraries/intentIqUtils/handleAdditionalParams.js'; +import { gamPredictionReport } from '../libraries/intentIqUtils/gamPredictionReport.js'; -const MODULE_NAME = 'iiqAnalytics' +const MODULE_NAME = 'iiqAnalytics'; const analyticsType = 'endpoint'; -const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_NAME}); +const storage = getStorageManager({ + moduleType: MODULE_TYPE_ANALYTICS, + moduleName: MODULE_NAME +}); const prebidVersion = '$prebid.version$'; export const REPORTER_ID = Date.now() + '_' + getRandom(0, 1000); const allowedStorage = defineStorageType(config.enabledStorageTypes); +let globalName; +let alreadySubscribedOnGAM = false; +let reportList = {}; +let cleanReportsID; const PARAMS_NAMES = { abTestGroup: 'abGroup', @@ -61,20 +74,20 @@ const PARAMS_NAMES = { }; function getIntentIqConfig() { - return config.getConfig('userSync.userIds')?.find(m => m.name === 'intentIqId'); + return config.getConfig('userSync.userIds')?.find((m) => m.name === 'intentIqId'); } -const DEFAULT_URL = 'https://reports.intentiq.com/report' +const DEFAULT_URL = 'https://reports.intentiq.com/report'; const getDataForDefineURL = () => { - const iiqConfig = getIntentIqConfig() + const iiqConfig = getIntentIqConfig(); const cmpData = getCmpData(); const gdprDetected = cmpData.gdprString; - return [iiqConfig, gdprDetected] -} + return [iiqConfig, gdprDetected]; +}; -const iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({url: DEFAULT_URL, analyticsType}), { +const iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ url: DEFAULT_URL, analyticsType }), { initOptions: { lsValueInitialized: false, partner: null, @@ -89,13 +102,19 @@ const iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({url: DEFAULT_URL, an reportMethod: null, additionalParams: null }, - track({eventType, args}) { + track({ eventType, args }) { switch (eventType) { case BID_WON: bidWon(args); break; case BID_REQUESTED: + checkAndInitConfig(); defineGlobalVariableName(); + if (!alreadySubscribedOnGAM && shouldSubscribeOnGAM()) { + alreadySubscribedOnGAM = true; + const iiqConfig = getIntentIqConfig(); + gamPredictionReport(iiqConfig?.params?.gamObjectReference, bidWon); + } break; default: break; @@ -104,26 +123,26 @@ const iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({url: DEFAULT_URL, an }); // Events needed -const { - BID_WON, - BID_REQUESTED -} = EVENTS; +const { BID_WON, BID_REQUESTED } = EVENTS; function initAdapterConfig() { if (iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized) return; - const iiqConfig = getIntentIqConfig() + const iiqConfig = getIntentIqConfig(); if (iiqConfig) { iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized = true; iiqAnalyticsAnalyticsAdapter.initOptions.partner = - iiqConfig.params?.partner && !isNaN(iiqConfig.params.partner) ? iiqConfig.params.partner : -1; + iiqConfig.params?.partner && !isNaN(iiqConfig.params.partner) ? iiqConfig.params.partner : -1; iiqAnalyticsAnalyticsAdapter.initOptions.browserBlackList = - typeof iiqConfig.params?.browserBlackList === 'string' ? iiqConfig.params.browserBlackList.toLowerCase() : ''; - iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = iiqConfig.params?.manualWinReportEnabled || false; + typeof iiqConfig.params?.browserBlackList === 'string' + ? iiqConfig.params.browserBlackList.toLowerCase() + : ''; + iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = + iiqConfig.params?.manualWinReportEnabled || false; iiqAnalyticsAnalyticsAdapter.initOptions.domainName = iiqConfig.params?.domainName || ''; iiqAnalyticsAnalyticsAdapter.initOptions.siloEnabled = - typeof iiqConfig.params?.siloEnabled === 'boolean' ? iiqConfig.params.siloEnabled : false; + typeof iiqConfig.params?.siloEnabled === 'boolean' ? iiqConfig.params.siloEnabled : false; iiqAnalyticsAnalyticsAdapter.initOptions.reportMethod = parseReportingMethod(iiqConfig.params?.reportMethod); iiqAnalyticsAnalyticsAdapter.initOptions.additionalParams = iiqConfig.params?.additionalParams || null; } else { @@ -136,20 +155,31 @@ function initAdapterConfig() { function initReadLsIds() { try { iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs = null; - iiqAnalyticsAnalyticsAdapter.initOptions.fpid = JSON.parse(readData( - `${FIRST_PARTY_KEY}${iiqAnalyticsAnalyticsAdapter.initOptions.siloEnabled ? '_p_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner : ''}`, - allowedStorage, storage - )); + iiqAnalyticsAnalyticsAdapter.initOptions.fpid = JSON.parse( + readData( + `${FIRST_PARTY_KEY}${ + iiqAnalyticsAnalyticsAdapter.initOptions.siloEnabled + ? '_p_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner + : '' + }`, + allowedStorage, + storage + ) + ); if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid) { iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup = iiqAnalyticsAnalyticsAdapter.initOptions.fpid.group; } - const partnerData = readData(FIRST_PARTY_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner, allowedStorage, storage); + const partnerData = readData( + FIRST_PARTY_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner, + allowedStorage, + storage + ); const clientsHints = readData(CLIENT_HINTS_KEY, allowedStorage, storage) || ''; if (partnerData) { iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized = true; const pData = JSON.parse(partnerData); - iiqAnalyticsAnalyticsAdapter.initOptions.terminationCause = pData.terminationCause + iiqAnalyticsAnalyticsAdapter.initOptions.terminationCause = pData.terminationCause; iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs = pData.data; iiqAnalyticsAnalyticsAdapter.initOptions.eidl = pData.eidl || -1; iiqAnalyticsAnalyticsAdapter.initOptions.clientType = pData.clientType || null; @@ -158,34 +188,79 @@ function initReadLsIds() { iiqAnalyticsAnalyticsAdapter.initOptions.rrtt = pData.rrtt || null; } - iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints = clientsHints + iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints = clientsHints; } catch (e) { - logError(e) + logError(e); } } -function bidWon(args, isReportExternal) { +function shouldSubscribeOnGAM() { + const iiqConfig = getIntentIqConfig(); + if (!iiqConfig?.params?.gamObjectReference || !isPlainObject(iiqConfig.params.gamObjectReference)) return false; + const partnerDataFromLS = readData( + FIRST_PARTY_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner, + allowedStorage, + storage + ); + + if (partnerDataFromLS) { + const partnerData = JSON.parse(partnerDataFromLS); + return partnerData.gpr || (!('gpr' in partnerData) && iiqConfig?.params?.gamPredictReporting); + } + return false; +} + +function shouldSendReport(isReportExternal) { + return ( + (isReportExternal && + iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled && + !shouldSubscribeOnGAM()) || + (!isReportExternal && !iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled) + ); +} + +export function restoreReportList() { + reportList = {}; +} + +function checkAndInitConfig() { if (!iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized) { initAdapterConfig(); } +} - if (isNaN(iiqAnalyticsAnalyticsAdapter.initOptions.partner) || iiqAnalyticsAnalyticsAdapter.initOptions.partner === -1) return; +function bidWon(args, isReportExternal) { + checkAndInitConfig(); + if ( + isNaN(iiqAnalyticsAnalyticsAdapter.initOptions.partner) || + iiqAnalyticsAnalyticsAdapter.initOptions.partner === -1 + ) { + return; + } const currentBrowserLowerCase = detectBrowser(); if (iiqAnalyticsAnalyticsAdapter.initOptions.browserBlackList?.includes(currentBrowserLowerCase)) { logError('IIQ ANALYTICS -> Browser is in blacklist!'); return; } - if (iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized && !iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized) { + if ( + iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized && + !iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized + ) { initReadLsIds(); } - if ((isReportExternal && iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled) || (!isReportExternal && !iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled)) { - const { url, method, payload } = constructFullUrl(preparePayload(args, true)); + if (shouldSendReport(isReportExternal)) { + const preparedPayload = preparePayload(args, true); + if (!preparedPayload) return false; + const { url, method, payload } = constructFullUrl(preparedPayload); if (method === 'POST') { - ajax(url, undefined, payload, {method, contentType: 'application/x-www-form-urlencoded'}); + ajax(url, undefined, payload, { + method, + contentType: 'application/x-www-form-urlencoded' + }); } else { - ajax(url, undefined, null, {method}); + ajax(url, undefined, null, { method }); } logInfo('IIQ ANALYTICS -> BID WON'); return true; @@ -214,17 +289,18 @@ function defineGlobalVariableName() { const iiqConfig = getIntentIqConfig(); const partnerId = iiqConfig?.params?.partner || 0; + globalName = `intentIqAnalyticsAdapter_${partnerId}`; - window[`intentIqAnalyticsAdapter_${partnerId}`] = { reportExternalWin }; + window[globalName] = { reportExternalWin }; } function getRandom(start, end) { - return Math.floor((Math.random() * (end - start + 1)) + start); + return Math.floor(Math.random() * (end - start + 1) + start); } export function preparePayload(data) { const result = getDefaultDataObject(); - readData(FIRST_PARTY_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner, allowedStorage, storage); + result[PARAMS_NAMES.partnerId] = iiqAnalyticsAnalyticsAdapter.initOptions.partner; result[PARAMS_NAMES.prebidVersion] = prebidVersion; result[PARAMS_NAMES.referrer] = getReferrer(); @@ -238,11 +314,27 @@ export function preparePayload(data) { result[PARAMS_NAMES.isInTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup === 'A'; result[PARAMS_NAMES.agentId] = REPORTER_ID; - if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid?.pcid) result[PARAMS_NAMES.firstPartyId] = encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid); - if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid?.pid) result[PARAMS_NAMES.profile] = encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pid) - + if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid?.pcid) { + result[PARAMS_NAMES.firstPartyId] = encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid); + } + if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid?.pid) { + result[PARAMS_NAMES.profile] = encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pid); + } prepareData(data, result); + if (!reportList[result.placementId] || !reportList[result.placementId][result.prebidAuctionId]) { + reportList[result.placementId] = reportList[result.placementId] + ? { ...reportList[result.placementId], [result.prebidAuctionId]: 1 } + : { [result.prebidAuctionId]: 1 }; + cleanReportsID = setTimeout(() => { + if (cleanReportsID) clearTimeout(cleanReportsID); + restoreReportList(); + }, 1500); // clear object in 1.5 second after defining reporting list + } else { + logError('Duplication detected, report will be not sent'); + return; + } + fillEidsData(result); return result; @@ -250,12 +342,13 @@ export function preparePayload(data) { function fillEidsData(result) { if (iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized) { - result[PARAMS_NAMES.hadEidsInLocalStorage] = iiqAnalyticsAnalyticsAdapter.initOptions.eidl && iiqAnalyticsAnalyticsAdapter.initOptions.eidl > 0; + result[PARAMS_NAMES.hadEidsInLocalStorage] = + iiqAnalyticsAnalyticsAdapter.initOptions.eidl && iiqAnalyticsAnalyticsAdapter.initOptions.eidl > 0; result[PARAMS_NAMES.auctionEidsLength] = iiqAnalyticsAnalyticsAdapter.initOptions.eidl || -1; } } -function prepareData (data, result) { +function prepareData(data, result) { const adTypeValue = data.adType || data.mediaType; if (data.bidderCode) { @@ -276,9 +369,9 @@ function prepareData (data, result) { if (data.status) { result.status = data.status; } - if (data.auctionId) { - result.prebidAuctionId = data.auctionId; - } + + result.prebidAuctionId = data.auctionId || data.prebidAuctionId; + if (adTypeValue) { result[PARAMS_NAMES.adType] = adTypeValue; } @@ -307,7 +400,7 @@ function prepareData (data, result) { result.placementId = data.adUnitCode || extractPlacementId(data) || ''; } - result.biddingPlatformId = 1; + result.biddingPlatformId = data.biddingPlatformId || 1; result.partnerAuctionId = 'BW'; } @@ -327,18 +420,18 @@ function extractPlacementId(data) { function getDefaultDataObject() { return { - 'inbbl': false, - 'pbjsver': prebidVersion, - 'partnerAuctionId': 'BW', - 'reportSource': 'pbjs', - 'abGroup': 'U', - 'jsversion': VERSION, - 'partnerId': -1, - 'biddingPlatformId': 1, - 'idls': false, - 'ast': -1, - 'aeidln': -1 - } + inbbl: false, + pbjsver: prebidVersion, + partnerAuctionId: 'BW', + reportSource: 'pbjs', + abGroup: 'U', + jsversion: VERSION, + partnerId: -1, + biddingPlatformId: 1, + idls: false, + ast: -1, + aeidln: -1 + }; } function constructFullUrl(data) { @@ -351,27 +444,38 @@ function constructFullUrl(data) { const cmpData = getCmpData(); const baseUrl = reportingServerAddress(...getDataForDefineURL()); - let url = baseUrl + '?pid=' + iiqAnalyticsAnalyticsAdapter.initOptions.partner + - '&mct=1' + - ((iiqAnalyticsAnalyticsAdapter.initOptions?.fpid) - ? '&iiqid=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid) : '') + - '&agid=' + REPORTER_ID + - '&jsver=' + VERSION + - '&source=' + PREBID + - '&uh=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints) + - (cmpData.uspString ? '&us_privacy=' + encodeURIComponent(cmpData.uspString) : '') + - (cmpData.gppString ? '&gpp=' + encodeURIComponent(cmpData.gppString) : '') + - (cmpData.gdprString - ? '&gdpr_consent=' + encodeURIComponent(cmpData.gdprString) + '&gdpr=1' - : '&gdpr=0'); - url = appendSPData(url, iiqAnalyticsAnalyticsAdapter.initOptions.fpid) + let url = + baseUrl + + '?pid=' + + iiqAnalyticsAnalyticsAdapter.initOptions.partner + + '&mct=1' + + (iiqAnalyticsAnalyticsAdapter.initOptions?.fpid + ? '&iiqid=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid) + : '') + + '&agid=' + + REPORTER_ID + + '&jsver=' + + VERSION + + '&source=' + + PREBID + + '&uh=' + + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints) + + (cmpData.uspString ? '&us_privacy=' + encodeURIComponent(cmpData.uspString) : '') + + (cmpData.gppString ? '&gpp=' + encodeURIComponent(cmpData.gppString) : '') + + (cmpData.gdprString ? '&gdpr_consent=' + encodeURIComponent(cmpData.gdprString) + '&gdpr=1' : '&gdpr=0'); + url = appendSPData(url, iiqAnalyticsAnalyticsAdapter.initOptions.fpid); url = appendVrrefAndFui(url, iiqAnalyticsAnalyticsAdapter.initOptions.domainName); if (reportMethod === 'POST') { return { url, method: 'POST', payload: JSON.stringify(report) }; } url += '&payload=' + encodeURIComponent(JSON.stringify(report)); - url = handleAdditionalParams(currentBrowserLowerCase, url, 2, iiqAnalyticsAnalyticsAdapter.initOptions.additionalParams); + url = handleAdditionalParams( + currentBrowserLowerCase, + url, + 2, + iiqAnalyticsAnalyticsAdapter.initOptions.additionalParams + ); return { url, method: 'GET' }; } diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 3332455d3cd..5c53d9e5905 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -592,6 +592,9 @@ export const intentIqIdSubmodule = { // server provided data firstPartyData.spd = respJson.spd; } + if ('gpr' in respJson) { + partnerData.gpr = respJson.gpr; + } if (rrttStrtTime && rrttStrtTime > 0) { partnerData.rrtt = Date.now() - rrttStrtTime; diff --git a/modules/intentIqIdSystem.md b/modules/intentIqIdSystem.md index 42acf8a0600..a9a9badd462 100644 --- a/modules/intentIqIdSystem.md +++ b/modules/intentIqIdSystem.md @@ -15,7 +15,7 @@ By leveraging the Intent IQ identity graph, our module helps publishers, SSPs, a ## Registration Navigate to [our portal](https://www.intentiq.com/) and contact our team for partner ID. -check our [documentation](https://pbmodule.documents.intentiq.com/) to get more information about our solution and how utilze it's full potential +check our [documentation](https://pbmodule.documents.intentiq.com/) to get more information about our solution and how to utilze it's full potential ## Integration @@ -47,6 +47,7 @@ Please find below list of parameters that could be used in configuring Intent IQ | params.domainName | Optional | String | Specifies the domain of the page in which the IntentIQ object is currently running and serving the impression. This domain will be used later in the revenue reporting breakdown by domain. For example, cnn.com. It identifies the primary source of requests to the IntentIQ servers, even within nested web pages. | `"currentDomain.com"` | | params.gamObjectReference | Optional | Object | This is a reference to the Google Ad Manager (GAM) object, which will be used to set targeting. If this parameter is not provided, the group reporting will not be configured. | `googletag` | | params.gamParameterName | Optional | String | The name of the targeting parameter that will be used to pass the group. If not specified, the default value is `intent_iq_group`. | `"intent_iq_group"` | +| params.gamPredictReporting | Optional | Boolean | This variable controls whether the GAM prediction logic is enabled or disabled. | `false` | | params.adUnitConfig | Optional | Number | Determines how the `placementId` parameter is extracted in the report (default is 1). Possible values: 1 – adUnitCode first, 2 – placementId first, 3 – only adUnitCode, 4 – only placementId | `1` | | params.sourceMetaData | Optional | String | This metadata can be provided by the partner and will be included in the requests URL as a query parameter | `"123.123.123.123"` | | params.sourceMetaDataExternal | Optional | Number | This metadata can be provided by the partner and will be included in the requests URL as a query parameter | `123456` | @@ -79,10 +80,12 @@ pbjs.setConfig({ domainName: "currentDomain.com", gamObjectReference: googletag, // Optional parameter gamParameterName: "intent_iq_group", // Optional parameter + gamPredictReporting: false, // Optional parameter adUnitConfig: 1, // Extracting placementId strategy (adUnitCode or placementId order of priorities) sourceMetaData: "123.123.123.123", // Optional parameter sourceMetaDataExternal: 123456, // Optional parameter reportMethod: "GET", // Optional parameter + chTimeout: 10, // Optional parameter additionalParameters: [ // Optional parameter { parameterName: "abc", diff --git a/test/spec/modules/intentIqAnalyticsAdapter_spec.js b/test/spec/modules/intentIqAnalyticsAdapter_spec.js index 76ecabf3460..198f240497c 100644 --- a/test/spec/modules/intentIqAnalyticsAdapter_spec.js +++ b/test/spec/modules/intentIqAnalyticsAdapter_spec.js @@ -7,7 +7,7 @@ import { EVENTS } from 'src/constants.js'; import * as events from 'src/events.js'; import { getStorageManager } from 'src/storageManager.js'; import sinon from 'sinon'; -import { REPORTER_ID, preparePayload } from '../../../modules/intentIqAnalyticsAdapter.js'; +import { REPORTER_ID, preparePayload, restoreReportList } from '../../../modules/intentIqAnalyticsAdapter.js'; import {FIRST_PARTY_KEY, PREBID, VERSION} from '../../../libraries/intentIqConstants/intentIqConstants.js'; import * as detectBrowserUtils from '../../../libraries/intentIqUtils/detectBrowserUtils.js'; import {getReferrer, appendVrrefAndFui} from '../../../libraries/intentIqUtils/getRefferer.js'; @@ -22,6 +22,8 @@ const REPORT_SERVER_ADDRESS = 'https://test-reports.intentiq.com/report'; const storage = getStorageManager({ moduleType: 'analytics', moduleName: 'iiqAnalytics' }); +const randomVal = () => Math.floor(Math.random() * 100000) + 1 + const getUserConfig = () => [ { 'name': 'intentIqId', @@ -57,7 +59,7 @@ const getUserConfigWithReportingServerAddress = () => [ } ]; -const wonRequest = { +const getWonRequest = () => ({ 'bidderCode': 'pubmatic', 'width': 728, 'height': 90, @@ -65,7 +67,7 @@ const wonRequest = { 'adId': '23caeb34c55da51', 'requestId': '87615b45ca4973', 'transactionId': '5e69fd76-8c86-496a-85ce-41ae55787a50', - 'auctionId': '0cbd3a43-ff45-47b8-b002-16d3946b23bf', + 'auctionId': '0cbd3a43-ff45-47b8-b002-16d3946b23bf-' + randomVal(), 'mediaType': 'banner', 'source': 'client', 'cpm': 5, @@ -87,7 +89,7 @@ const wonRequest = { 'pbCg': '', 'size': '728x90', 'status': 'rendered' -}; +}); describe('IntentIQ tests all', function () { let logErrorStub; @@ -140,6 +142,7 @@ describe('IntentIQ tests all', function () { it('should send POST request with payload in request body if reportMethod is POST', function () { const [userConfig] = getUserConfig(); userConfig.params.reportMethod = 'POST'; + const wonRequest = getWonRequest(); config.getConfig.restore(); sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns([userConfig]); @@ -149,6 +152,7 @@ describe('IntentIQ tests all', function () { events.emit(EVENTS.BID_WON, wonRequest); const request = server.requests[0]; + restoreReportList(); const expectedData = preparePayload(wonRequest); const expectedPayload = `["${btoa(JSON.stringify(expectedData))}"]`; @@ -159,6 +163,7 @@ describe('IntentIQ tests all', function () { it('should send GET request with payload in query string if reportMethod is NOT provided', function () { const [userConfig] = getUserConfig(); + const wonRequest = getWonRequest(); config.getConfig.restore(); sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns([userConfig]); @@ -173,6 +178,7 @@ describe('IntentIQ tests all', function () { const payloadEncoded = url.searchParams.get('payload'); const decoded = JSON.parse(atob(JSON.parse(payloadEncoded)[0])); + restoreReportList(); const expected = preparePayload(wonRequest); expect(decoded.partnerId).to.equal(expected.partnerId); @@ -184,7 +190,7 @@ describe('IntentIQ tests all', function () { localStorage.setItem(FIRST_PARTY_KEY, defaultData); getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({href: 'http://localhost:9876'}); const expectedVrref = getWindowLocationStub().href; - events.emit(EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, getWonRequest()); expect(server.requests.length).to.be.above(0); const request = server.requests[0]; @@ -200,7 +206,7 @@ describe('IntentIQ tests all', function () { it('should include adType in payload when present in BID_WON event', function () { localStorage.setItem(FIRST_PARTY_KEY, defaultData); getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876/' }); - const bidWonEvent = { ...wonRequest, mediaType: 'video' }; + const bidWonEvent = { ...getWonRequest(), mediaType: 'video' }; events.emit(EVENTS.BID_WON, bidWonEvent); @@ -241,7 +247,7 @@ describe('IntentIQ tests all', function () { const uspStub = sinon.stub(uspDataHandler, 'getConsentData').returns('1NYN'); const gdprStub = sinon.stub(gdprDataHandler, 'getConsentData').returns({ consentString: 'gdprConsent' }); - events.emit(EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, getWonRequest()); expect(server.requests.length).to.be.above(0); const request = server.requests[0]; @@ -260,7 +266,7 @@ describe('IntentIQ tests all', function () { localStorage.setItem(FIRST_PARTY_KEY, '{"pcid":"testpcid", "group": "B"}'); const expectedVrref = encodeURIComponent('http://localhost:9876/'); - events.emit(EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, getWonRequest()); expect(server.requests.length).to.be.above(0); const request = server.requests[0]; @@ -273,11 +279,13 @@ describe('IntentIQ tests all', function () { it('should handle BID_WON event with default group configuration', function () { localStorage.setItem(FIRST_PARTY_KEY, defaultData); const defaultDataObj = JSON.parse(defaultData) + const wonRequest = getWonRequest(); events.emit(EVENTS.BID_WON, wonRequest); expect(server.requests.length).to.be.above(0); const request = server.requests[0]; + restoreReportList() const dataToSend = preparePayload(wonRequest); const base64String = btoa(JSON.stringify(dataToSend)); const payload = encodeURIComponent(JSON.stringify([base64String])); @@ -301,7 +309,7 @@ describe('IntentIQ tests all', function () { getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876/' }); - events.emit(EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, getWonRequest()); expect(server.requests.length).to.be.above(0); const request = server.requests[0]; @@ -317,14 +325,14 @@ describe('IntentIQ tests all', function () { it('should not send request if manualWinReportEnabled is true', function () { iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = true; - events.emit(EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, getWonRequest()); expect(server.requests.length).to.equal(1); }); it('should read data from local storage', function () { localStorage.setItem(FIRST_PARTY_KEY, '{"group": "A"}'); localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, '{"data":"testpcid", "eidl": 10}'); - events.emit(EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, getWonRequest()); expect(iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs).to.equal('testpcid'); expect(iiqAnalyticsAnalyticsAdapter.initOptions.eidl).to.equal(10); expect(iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup).to.equal('A'); @@ -333,14 +341,14 @@ describe('IntentIQ tests all', function () { it('should handle initialization values from local storage', function () { localStorage.setItem(FIRST_PARTY_KEY, '{"pcid":"testpcid", "group": "B"}'); localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, '{"data":"testpcid"}'); - events.emit(EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, getWonRequest()); expect(iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup).to.equal('B'); expect(iiqAnalyticsAnalyticsAdapter.initOptions.fpid).to.be.not.null; }); it('should handle reportExternalWin', function () { events.emit(EVENTS.BID_REQUESTED); - iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = true; + iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = false; localStorage.setItem(FIRST_PARTY_KEY, '{"pcid":"testpcid", "group": "B"}'); localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, '{"data":"testpcid"}'); expect(window[`intentIqAnalyticsAdapter_${partner}`].reportExternalWin).to.be.a('function'); @@ -387,7 +395,7 @@ describe('IntentIQ tests all', function () { detectBrowserStub = sinon.stub(detectBrowserUtils, 'detectBrowser').returns('chrome'); localStorage.setItem(FIRST_PARTY_KEY, defaultData); - events.emit(EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, getWonRequest()); expect(server.requests.length).to.equal(0); }); @@ -401,7 +409,7 @@ describe('IntentIQ tests all', function () { detectBrowserStub = sinon.stub(detectBrowserUtils, 'detectBrowser').returns('safari'); localStorage.setItem(FIRST_PARTY_KEY, defaultData); - events.emit(EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, getWonRequest()); expect(server.requests.length).to.be.above(0); const request = server.requests[0]; @@ -421,7 +429,7 @@ describe('IntentIQ tests all', function () { detectBrowserStub = sinon.stub(detectBrowserUtils, 'detectBrowser').returns('safari'); localStorage.setItem(FIRST_PARTY_KEY, defaultData); - events.emit(EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, getWonRequest()); expect(server.requests.length).to.be.above(0); const request = server.requests[0]; @@ -431,7 +439,7 @@ describe('IntentIQ tests all', function () { it('should include source parameter in report URL', function () { localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify(defaultData)); - events.emit(EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, getWonRequest()); const request = server.requests[0]; expect(server.requests.length).to.be.above(0); @@ -447,7 +455,7 @@ describe('IntentIQ tests all', function () { sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns(USERID_CONFIG); localStorage.setItem(FIRST_PARTY_KEY, `${FIRST_PARTY_KEY}${siloEnabled ? '_p_' + partner : ''}`); - events.emit(EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, getWonRequest()); expect(server.requests.length).to.be.above(0); const request = server.requests[0]; @@ -466,7 +474,7 @@ describe('IntentIQ tests all', function () { sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns(userConfig); localStorage.setItem(FIRST_PARTY_KEY, defaultData); - events.emit(EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, getWonRequest()); const request = server.requests[0]; expect(request.url).to.include('general=Lee'); @@ -485,7 +493,7 @@ describe('IntentIQ tests all', function () { sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns(userConfig); localStorage.setItem(FIRST_PARTY_KEY, defaultData); - events.emit(EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, getWonRequest()); const request = server.requests[0]; expect(request.url).not.to.include('general'); @@ -497,7 +505,7 @@ describe('IntentIQ tests all', function () { localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify({...defaultData, spd: spdObject})); getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876/' }); - events.emit(EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, getWonRequest()); const request = server.requests[0]; @@ -512,7 +520,7 @@ describe('IntentIQ tests all', function () { localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify({...defaultData, spd: spdObject})); getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876/' }); - events.emit(EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, getWonRequest()); const request = server.requests[0]; @@ -520,6 +528,97 @@ describe('IntentIQ tests all', function () { expect(request.url).to.include(`&spd=${expectedSpdEncoded}`); }); + describe('GAM prediction reporting', function () { + function createMockGAM() { + const listeners = {}; + return { + cmd: [], + pubads: () => ({ + addEventListener: (name, cb) => { + listeners[name] = cb; + } + }), + _listeners: listeners + }; + } + + function withConfigGamPredict(gamObj) { + const [userConfig] = getUserConfig(); + userConfig.params.gamObjectReference = gamObj; + userConfig.params.gamPredictReporting = true; + config.getConfig.restore(); + sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns([userConfig]); + } + + it('should subscribe to GAM and send report on slotRenderEnded without prior bidWon', function () { + const gam = createMockGAM(); + withConfigGamPredict(gam); + + // enable subscription by LS flag + localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, JSON.stringify({ gpr: true })); + localStorage.setItem(FIRST_PARTY_KEY, defaultData); + + // provide recent auctionEnd with matching bid to enrich payload + events.getEvents.restore(); + sinon.stub(events, 'getEvents').returns([ + { eventType: 'auctionEnd', args: { + auctionId: 'auc-1', + adUnitCodes: ['ad-unit-1'], + bidsReceived: [{ bidder: 'pubmatic', adUnitCode: 'ad-unit-1', cpm: 1, currency: 'USD', originalCpm: 1, originalCurrency: 'USD', status: 'rendered' }] + }} + ]); + + // trigger adapter to subscribe + events.emit(EVENTS.BID_REQUESTED); + + // execute GAM cmd to register listener + gam.cmd.forEach(fn => fn()); + + // simulate slotRenderEnded + const slot = { + getSlotElementId: () => 'ad-unit-1', + getAdUnitPath: () => '/123/foo', + getTargetingKeys: () => ['hb_bidder', 'hb_adid'], + getTargeting: (k) => k === 'hb_bidder' ? ['pubmatic'] : k === 'hb_adid' ? ['ad123'] : [] + }; + if (gam._listeners['slotRenderEnded']) { + gam._listeners['slotRenderEnded']({ isEmpty: false, slot }); + } + + expect(server.requests.length).to.be.above(0); + }); + + it('should NOT send report if a matching bidWon already exists', function () { + const gam = createMockGAM(); + withConfigGamPredict(gam); + + localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, JSON.stringify({ gpr: true })); + localStorage.setItem(FIRST_PARTY_KEY, defaultData); + + // provide prior bidWon matching placementId and hb_adid + events.getEvents.restore(); + sinon.stub(events, 'getEvents').returns([ + { eventType: 'bidWon', args: { adId: 'ad123' }, id: 'ad-unit-1' } + ]); + + events.emit(EVENTS.BID_REQUESTED); + gam.cmd.forEach(fn => fn()); + + const slot = { + getSlotElementId: () => 'ad-unit-1', + getAdUnitPath: () => '/123/foo', + getTargetingKeys: () => ['hb_bidder', 'hb_adid'], + getTargeting: (k) => k === 'hb_bidder' ? ['pubmatic'] : k === 'hb_adid' ? ['ad123'] : [] + }; + + const initialRequests = server.requests.length; + if (gam._listeners['slotRenderEnded']) { + gam._listeners['slotRenderEnded']({ isEmpty: false, slot }); + } + expect(server.requests.length).to.equal(initialRequests); + }); + }); + const testCasesVrref = [ { description: 'domainName matches window.top.location.href', @@ -641,7 +740,7 @@ describe('IntentIQ tests all', function () { config.getConfig.restore(); sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns([userConfig]); - const testEvent = { ...wonRequest, ...event }; + const testEvent = { ...getWonRequest(), ...event }; events.emit(EVENTS.BID_WON, testEvent); const request = server.requests[0]; From 71b67ab6de1d31c63a8996ecfe6cbcdce2050441 Mon Sep 17 00:00:00 2001 From: DimaIntentIQ Date: Mon, 20 Oct 2025 16:04:16 +0300 Subject: [PATCH 4/7] remove GAM prediction flag --- modules/intentIqIdSystem.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 5c53d9e5905..53755afa050 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -593,7 +593,10 @@ export const intentIqIdSubmodule = { firstPartyData.spd = respJson.spd; } if ('gpr' in respJson) { + // GAM prediction reporting partnerData.gpr = respJson.gpr; + } else { + delete partnerData.gpr // remove prediction flag in case server doesn't provide it } if (rrttStrtTime && rrttStrtTime > 0) { From e8e41d31070fa0a783e2046a06ab3cf2748d53d3 Mon Sep 17 00:00:00 2001 From: DimaIntentIQ Date: Wed, 22 Oct 2025 14:52:59 +0300 Subject: [PATCH 5/7] extend GAM prediction documentation --- modules/intentIqIdSystem.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/intentIqIdSystem.md b/modules/intentIqIdSystem.md index a9a9badd462..b8f0dec32d8 100644 --- a/modules/intentIqIdSystem.md +++ b/modules/intentIqIdSystem.md @@ -47,7 +47,7 @@ Please find below list of parameters that could be used in configuring Intent IQ | params.domainName | Optional | String | Specifies the domain of the page in which the IntentIQ object is currently running and serving the impression. This domain will be used later in the revenue reporting breakdown by domain. For example, cnn.com. It identifies the primary source of requests to the IntentIQ servers, even within nested web pages. | `"currentDomain.com"` | | params.gamObjectReference | Optional | Object | This is a reference to the Google Ad Manager (GAM) object, which will be used to set targeting. If this parameter is not provided, the group reporting will not be configured. | `googletag` | | params.gamParameterName | Optional | String | The name of the targeting parameter that will be used to pass the group. If not specified, the default value is `intent_iq_group`. | `"intent_iq_group"` | -| params.gamPredictReporting | Optional | Boolean | This variable controls whether the GAM prediction logic is enabled or disabled. | `false` | +| params.gamPredictReporting | Optional | Boolean | This variable controls whether the GAM prediction logic is enabled or disabled. The main purpose of this logic is to extract information from a rendered GAM slot when no Prebid bidWon event is available. In that case, we take the highest CPM from the current auction and add 0.01 to that value. | `false` | | params.adUnitConfig | Optional | Number | Determines how the `placementId` parameter is extracted in the report (default is 1). Possible values: 1 – adUnitCode first, 2 – placementId first, 3 – only adUnitCode, 4 – only placementId | `1` | | params.sourceMetaData | Optional | String | This metadata can be provided by the partner and will be included in the requests URL as a query parameter | `"123.123.123.123"` | | params.sourceMetaDataExternal | Optional | Number | This metadata can be provided by the partner and will be included in the requests URL as a query parameter | `123456` | From de336b67a49ff1025238d9379ab0be22ba79b447 Mon Sep 17 00:00:00 2001 From: DimaIntentIQ Date: Wed, 22 Oct 2025 14:56:53 +0300 Subject: [PATCH 6/7] fix configuration name in documentation --- modules/intentIqIdSystem.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/intentIqIdSystem.md b/modules/intentIqIdSystem.md index b8f0dec32d8..8e50c4198c6 100644 --- a/modules/intentIqIdSystem.md +++ b/modules/intentIqIdSystem.md @@ -58,10 +58,10 @@ Please find below list of parameters that could be used in configuring Intent IQ | params.siloEnabled | Optional | Boolean | Determines if first-party data is stored in a siloed storage key. When set to `true`, first-party data is stored under a modified key that appends `_p_` plus the partner value rather than using the default storage key. The default value is `false`. | `true` | | params.groupChanged | Optional | Function | A callback that is triggered every time the user’s A/B group is set or updated. |`(group) => console.log('Group changed:', group)` | | params.chTimeout | Optional | Number | Maximum time (in milliseconds) to wait for Client Hints from the browser before sending request. Default value is `10ms` | `30` | -| params.additionalParameters | Optional | Array | This parameter allows sending additional custom key-value parameters with specific destination logic (sync, VR, winreport). Each custom parameter is defined as an object in the array. | `[ { parameterName: “abc”, parameterValue: 123, destination: [1,1,0] } ]` | -| params.additionalParameters [0].parameterName | Required | String | Name of the custom parameter. This will be sent as a query parameter. | `"abc"` | -| params.additionalParameters [0].parameterValue | Required | String / Number | Value to assign to the parameter. | `123` | -| params.additionalParameters [0].destination | Required | Array | Array of numbers either `1` or `0`. Controls where this parameter is sent `[sendWithSync, sendWithVr, winreport]`. | `[1, 0, 0]` | +| params.additionalParams | Optional | Array | This parameter allows sending additional custom key-value parameters with specific destination logic (sync, VR, winreport). Each custom parameter is defined as an object in the array. | `[ { parameterName: “abc”, parameterValue: 123, destination: [1,1,0] } ]` | +| params.additionalParams [0].parameterName | Required | String | Name of the custom parameter. This will be sent as a query parameter. | `"abc"` | +| params.additionalParams [0].parameterValue | Required | String / Number | Value to assign to the parameter. | `123` | +| params.additionalParams [0].destination | Required | Array | Array of numbers either `1` or `0`. Controls where this parameter is sent `[sendWithSync, sendWithVr, winreport]`. | `[1, 0, 0]` | ### Configuration example @@ -86,7 +86,7 @@ pbjs.setConfig({ sourceMetaDataExternal: 123456, // Optional parameter reportMethod: "GET", // Optional parameter chTimeout: 10, // Optional parameter - additionalParameters: [ // Optional parameter + additionalParams: [ // Optional parameter { parameterName: "abc", parameterValue: 123, From 148e01e5adf8f648d0693980fab33f351ef531e4 Mon Sep 17 00:00:00 2001 From: DimaIntentIQ <139111483+DimaIntentIQ@users.noreply.github.com> Date: Tue, 28 Oct 2025 13:58:14 +0200 Subject: [PATCH 7/7] Agt 698 module config changes (#46) * move analytic related params to analytic adapter * Move back Maintainer section --- libraries/intentIqUtils/intentIqConfig.js | 2 +- modules/intentIqAnalyticsAdapter.js | 40 +++++++++-------- modules/intentIqAnalyticsAdapter.md | 38 +++++++++++----- modules/intentIqIdSystem.md | 9 ---- .../modules/intentIqAnalyticsAdapter_spec.js | 43 ++++++++++++------- 5 files changed, 77 insertions(+), 55 deletions(-) diff --git a/libraries/intentIqUtils/intentIqConfig.js b/libraries/intentIqUtils/intentIqConfig.js index 85c9111970b..3f2572f14fa 100644 --- a/libraries/intentIqUtils/intentIqConfig.js +++ b/libraries/intentIqUtils/intentIqConfig.js @@ -1,3 +1,3 @@ export const iiqServerAddress = (configParams, gdprDetected) => typeof configParams?.iiqServerAddress === 'string' ? configParams.iiqServerAddress : gdprDetected ? 'https://api-gdpr.intentiq.com' : 'https://api.intentiq.com' export const iiqPixelServerAddress = (configParams, gdprDetected) => typeof configParams?.iiqPixelServerAddress === 'string' ? configParams.iiqPixelServerAddress : gdprDetected ? 'https://sync-gdpr.intentiq.com' : 'https://sync.intentiq.com' -export const reportingServerAddress = (configParams, gdprDetected) => typeof configParams?.params?.reportingServerAddress === 'string' ? configParams.params.reportingServerAddress : gdprDetected ? 'https://reports-gdpr.intentiq.com/report' : 'https://reports.intentiq.com/report' +export const reportingServerAddress = (reportEndpoint, gdprDetected) => reportEndpoint && typeof reportEndpoint === 'string' ? reportEndpoint : gdprDetected ? 'https://reports-gdpr.intentiq.com/report' : 'https://reports.intentiq.com/report' diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js index e4caaaf1768..06c9bcb28b4 100644 --- a/modules/intentIqAnalyticsAdapter.js +++ b/modules/intentIqAnalyticsAdapter.js @@ -80,11 +80,10 @@ function getIntentIqConfig() { const DEFAULT_URL = 'https://reports.intentiq.com/report'; const getDataForDefineURL = () => { - const iiqConfig = getIntentIqConfig(); const cmpData = getCmpData(); const gdprDetected = cmpData.gdprString; - return [iiqConfig, gdprDetected]; + return [iiqAnalyticsAnalyticsAdapter.initOptions.reportingServerAddress, gdprDetected]; }; const iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ url: DEFAULT_URL, analyticsType }), { @@ -100,7 +99,8 @@ const iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ url: DEFAULT_URL, a domainName: null, siloEnabled: false, reportMethod: null, - additionalParams: null + additionalParams: null, + reportingServerAddress: '' }, track({ eventType, args }) { switch (eventType) { @@ -125,27 +125,32 @@ const iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ url: DEFAULT_URL, a // Events needed const { BID_WON, BID_REQUESTED } = EVENTS; -function initAdapterConfig() { +function initAdapterConfig(config) { if (iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized) return; - const iiqConfig = getIntentIqConfig(); + const iiqIdSystemConfig = getIntentIqConfig(); - if (iiqConfig) { + if (iiqIdSystemConfig) { + const { manualWinReportEnabled, gamPredictReporting, reportMethod, reportingServerAddress: reportEndpoint, adUnitConfig } = config?.options || {} iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized = true; iiqAnalyticsAnalyticsAdapter.initOptions.partner = - iiqConfig.params?.partner && !isNaN(iiqConfig.params.partner) ? iiqConfig.params.partner : -1; + iiqIdSystemConfig.params?.partner && !isNaN(iiqIdSystemConfig.params.partner) ? iiqIdSystemConfig.params.partner : -1; iiqAnalyticsAnalyticsAdapter.initOptions.browserBlackList = - typeof iiqConfig.params?.browserBlackList === 'string' - ? iiqConfig.params.browserBlackList.toLowerCase() + typeof iiqIdSystemConfig.params?.browserBlackList === 'string' + ? iiqIdSystemConfig.params.browserBlackList.toLowerCase() : ''; iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = - iiqConfig.params?.manualWinReportEnabled || false; - iiqAnalyticsAnalyticsAdapter.initOptions.domainName = iiqConfig.params?.domainName || ''; + manualWinReportEnabled || false; + iiqAnalyticsAnalyticsAdapter.initOptions.domainName = iiqIdSystemConfig.params?.domainName || ''; iiqAnalyticsAnalyticsAdapter.initOptions.siloEnabled = - typeof iiqConfig.params?.siloEnabled === 'boolean' ? iiqConfig.params.siloEnabled : false; - iiqAnalyticsAnalyticsAdapter.initOptions.reportMethod = parseReportingMethod(iiqConfig.params?.reportMethod); - iiqAnalyticsAnalyticsAdapter.initOptions.additionalParams = iiqConfig.params?.additionalParams || null; + typeof iiqIdSystemConfig.params?.siloEnabled === 'boolean' ? iiqIdSystemConfig.params.siloEnabled : false; + iiqAnalyticsAnalyticsAdapter.initOptions.reportMethod = parseReportingMethod(reportMethod); + iiqAnalyticsAnalyticsAdapter.initOptions.additionalParams = iiqIdSystemConfig.params?.additionalParams || null; + iiqAnalyticsAnalyticsAdapter.initOptions.gamPredictReporting = typeof gamPredictReporting === 'boolean' ? gamPredictReporting : false; + iiqAnalyticsAnalyticsAdapter.initOptions.reportingServerAddress = typeof reportEndpoint === 'string' ? reportEndpoint : ''; + iiqAnalyticsAnalyticsAdapter.initOptions.adUnitConfig = typeof adUnitConfig === 'number' ? adUnitConfig : 1; } else { + logError('IIQ ANALYTICS -> there is no initialized intentIqIdSystem module') iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized = false; iiqAnalyticsAnalyticsAdapter.initOptions.partner = -1; iiqAnalyticsAnalyticsAdapter.initOptions.reportMethod = 'GET'; @@ -205,7 +210,7 @@ function shouldSubscribeOnGAM() { if (partnerDataFromLS) { const partnerData = JSON.parse(partnerDataFromLS); - return partnerData.gpr || (!('gpr' in partnerData) && iiqConfig?.params?.gamPredictReporting); + return partnerData.gpr || (!('gpr' in partnerData) && iiqAnalyticsAnalyticsAdapter.initOptions.gamPredictReporting); } return false; } @@ -375,10 +380,8 @@ function prepareData(data, result) { if (adTypeValue) { result[PARAMS_NAMES.adType] = adTypeValue; } - const iiqConfig = getIntentIqConfig(); - const adUnitConfig = iiqConfig.params?.adUnitConfig; - switch (adUnitConfig) { + switch (iiqAnalyticsAnalyticsAdapter.initOptions.adUnitConfig) { case 1: // adUnitCode or placementId result.placementId = data.adUnitCode || extractPlacementId(data) || ''; @@ -483,6 +486,7 @@ iiqAnalyticsAnalyticsAdapter.originEnableAnalytics = iiqAnalyticsAnalyticsAdapte iiqAnalyticsAnalyticsAdapter.enableAnalytics = function (myConfig) { iiqAnalyticsAnalyticsAdapter.originEnableAnalytics(myConfig); // call the base class function + initAdapterConfig(myConfig) }; adapterManager.registerAnalyticsAdapter({ adapter: iiqAnalyticsAnalyticsAdapter, diff --git a/modules/intentIqAnalyticsAdapter.md b/modules/intentIqAnalyticsAdapter.md index 2f601658a3d..9389cb7d8ee 100644 --- a/modules/intentIqAnalyticsAdapter.md +++ b/modules/intentIqAnalyticsAdapter.md @@ -4,45 +4,61 @@ Module Name: iiqAnalytics Module Type: Analytics Adapter Maintainer: julian@intentiq.com -# Description +### Description By using this Intent IQ adapter, you will be able to obtain comprehensive analytics and metrics regarding the performance of the Intent IQ Unified ID module. This includes how the module impacts your revenue, CPMs, and fill rates related to bidders and domains. -## Intent IQ Universal ID Registration +#### Intent IQ Universal ID Registration No registration for this module is required. -## Intent IQ Universal IDConfiguration +#### Intent IQ Universal ID Configuration -IMPORTANT: only effective when Intent IQ Universal ID module is installed and configured. [(How-To)](https://docs.prebid.org/dev-docs/modules/userid-submodules/intentiq.html) +**IMPORTANT**: only effective when Intent IQ Universal ID module be installed and configured. [(How-To)](https://docs.prebid.org/dev-docs/modules/userid-submodules/intentiq.html) + +### Analytics Options + +{: .table .table-bordered .table-striped } +| Parameter | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| options.manualWinReportEnabled | Optional | Boolean | This variable determines whether the bidWon event is triggered automatically. If set to false, the event will occur automatically, and manual reporting with reportExternalWin will be disabled. If set to true, the event will not occur automatically, allowing manual reporting through reportExternalWin. The default value is false. | `false` | +| options.reportMethod | Optional | String | Defines the HTTP method used to send the analytics report. If set to `"POST"`, the report payload will be sent in the body of the request. If set to `"GET"` (default), the payload will be included as a query parameter in the request URL. | `"GET"` | +| options.reportingServerAddress | Optional | String | The base URL for the IntentIQ reporting server. If parameter is provided in `configParams`, it will be used. | `"https://domain.com"` | +| options.adUnitConfig | Optional | Number | Determines how the `placementId` parameter is extracted in the report (default is 1). Possible values: 1 – adUnitCode first, 2 – placementId first, 3 – only adUnitCode, 4 – only placementId. | `1` | +| options.gamPredictReporting | Optional | Boolean | This variable controls whether the GAM prediction logic is enabled or disabled. The main purpose of this logic is to extract information from a rendered GAM slot when no Prebid bidWon event is available. In that case, we take the highest CPM from the current auction and add 0.01 to that value. | `false` | #### Example Configuration ```js pbjs.enableAnalytics({ - provider: 'iiqAnalytics' + provider: 'iiqAnalytics', + options: { + manualWinReportEnabled: false, + reportMethod: "GET", + adUnitConfig: 1, + gamPredictReporting: false + } }); ``` - ### Manual Report Trigger with reportExternalWin The reportExternalWin function allows for manual reporting, meaning that reports will not be sent automatically but only when triggered manually. To enable this manual reporting functionality, you must set the manualWinReportEnabled parameter in Intent IQ Unified ID module configuration is true. Once enabled, reports can be manually triggered using the reportExternalWin function. - ### Calling the reportExternalWin Function To call the reportExternalWin function, you need to pass the partner_id parameter as shown in the example below: ```js -window.intentIqAnalyticsAdapter_[partner_id].reportExternalWin() +window.intentIqAnalyticsAdapter_[partner_id].reportExternalWin(reportData) ``` + Example use with Partner ID = 123455 ```js -window.intentIqAnalyticsAdapter_123455.reportExternalWin() +window.intentIqAnalyticsAdapter_123455.reportExternalWin(reportData) ``` ### Function Parameters @@ -60,11 +76,12 @@ currency: 'USD', // Currency for the CPM value. originalCpm: 1.5, // Original CPM value. originalCurrency: 'USD', // Original currency. status: 'rendered', // Auction status, e.g., 'rendered'. -placementId: 'div-1', // ID of the ad placement. +placementId: 'div-1' // ID of the ad placement. adType: 'banner' // Specifies the type of ad served } ``` +{: .table .table-bordered .table-striped } | Field | Data Type | Description | Example | Mandatory | |--------------------|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------|-----------| | biddingPlatformId | Integer | Specify the platform in which this ad impression was rendered – 1 – Prebid, 2 – Amazon, 3 – Google, 4 – Open RTB (including your local Prebid server) | 1 | Yes | @@ -79,7 +96,6 @@ adType: 'banner' // Specifies the type of ad served | placementId | String | Unique identifier of the ad unit on the webpage that showed this ad | div-1 | No | | adType | String | Specifies the type of ad served. Possible values: “banner“, “video“, “native“, “audio“. | banner | No | - To report the auction win, call the function as follows: ```js diff --git a/modules/intentIqIdSystem.md b/modules/intentIqIdSystem.md index 8e50c4198c6..bf561649566 100644 --- a/modules/intentIqIdSystem.md +++ b/modules/intentIqIdSystem.md @@ -43,18 +43,13 @@ Please find below list of parameters that could be used in configuring Intent IQ | params.callback | Optional | Function | This is a callback which is triggered with data | `(data) => console.log({ data })` | | params.timeoutInMillis | Optional | Number | This is the timeout in milliseconds, which defines the maximum duration before the callback is triggered. The default value is 500. | `450` | | params.browserBlackList | Optional | String | This is the name of a browser that can be added to a blacklist. | `"chrome"` | -| params.manualWinReportEnabled | Optional | Boolean | This variable determines whether the bidWon event is triggered automatically. If set to false, the event will occur automatically, and manual reporting with reportExternalWin will be disabled. If set to true, the event will not occur automatically, allowing manual reporting through reportExternalWin. The default value is false. | `true` | | params.domainName | Optional | String | Specifies the domain of the page in which the IntentIQ object is currently running and serving the impression. This domain will be used later in the revenue reporting breakdown by domain. For example, cnn.com. It identifies the primary source of requests to the IntentIQ servers, even within nested web pages. | `"currentDomain.com"` | | params.gamObjectReference | Optional | Object | This is a reference to the Google Ad Manager (GAM) object, which will be used to set targeting. If this parameter is not provided, the group reporting will not be configured. | `googletag` | | params.gamParameterName | Optional | String | The name of the targeting parameter that will be used to pass the group. If not specified, the default value is `intent_iq_group`. | `"intent_iq_group"` | -| params.gamPredictReporting | Optional | Boolean | This variable controls whether the GAM prediction logic is enabled or disabled. The main purpose of this logic is to extract information from a rendered GAM slot when no Prebid bidWon event is available. In that case, we take the highest CPM from the current auction and add 0.01 to that value. | `false` | -| params.adUnitConfig | Optional | Number | Determines how the `placementId` parameter is extracted in the report (default is 1). Possible values: 1 – adUnitCode first, 2 – placementId first, 3 – only adUnitCode, 4 – only placementId | `1` | | params.sourceMetaData | Optional | String | This metadata can be provided by the partner and will be included in the requests URL as a query parameter | `"123.123.123.123"` | | params.sourceMetaDataExternal | Optional | Number | This metadata can be provided by the partner and will be included in the requests URL as a query parameter | `123456` | | params.iiqServerAddress | Optional | String | The base URL for the IntentIQ API server. If parameter is provided in `configParams`, it will be used. | `"https://domain.com"` | | params.iiqPixelServerAddress | Optional | String | The base URL for the IntentIQ pixel synchronization server. If parameter is provided in `configParams`, it will be used. | `"https://domain.com"` | -| params.reportingServerAddress | Optional | String | The base URL for the IntentIQ reporting server. If parameter is provided in `configParams`, it will be used. | `"https://domain.com"` | -| params.reportMethod | Optional | String | Defines the HTTP method used to send the analytics report. If set to `"POST"`, the report payload will be sent in the body of the request. If set to `"GET"` (default), the payload will be included as a query parameter in the request URL. |`"GET"` | | params.siloEnabled | Optional | Boolean | Determines if first-party data is stored in a siloed storage key. When set to `true`, first-party data is stored under a modified key that appends `_p_` plus the partner value rather than using the default storage key. The default value is `false`. | `true` | | params.groupChanged | Optional | Function | A callback that is triggered every time the user’s A/B group is set or updated. |`(group) => console.log('Group changed:', group)` | | params.chTimeout | Optional | Number | Maximum time (in milliseconds) to wait for Client Hints from the browser before sending request. Default value is `10ms` | `30` | @@ -76,15 +71,11 @@ pbjs.setConfig({ browserBlackList: "chrome", callback: (data) => {...}, // your logic here groupChanged: (group) => console.log('Group is', group), - manualWinReportEnabled: true, // Optional parameter domainName: "currentDomain.com", gamObjectReference: googletag, // Optional parameter gamParameterName: "intent_iq_group", // Optional parameter - gamPredictReporting: false, // Optional parameter - adUnitConfig: 1, // Extracting placementId strategy (adUnitCode or placementId order of priorities) sourceMetaData: "123.123.123.123", // Optional parameter sourceMetaDataExternal: 123456, // Optional parameter - reportMethod: "GET", // Optional parameter chTimeout: 10, // Optional parameter additionalParams: [ // Optional parameter { diff --git a/test/spec/modules/intentIqAnalyticsAdapter_spec.js b/test/spec/modules/intentIqAnalyticsAdapter_spec.js index 198f240497c..664c6041ec8 100644 --- a/test/spec/modules/intentIqAnalyticsAdapter_spec.js +++ b/test/spec/modules/intentIqAnalyticsAdapter_spec.js @@ -8,9 +8,9 @@ import * as events from 'src/events.js'; import { getStorageManager } from 'src/storageManager.js'; import sinon from 'sinon'; import { REPORTER_ID, preparePayload, restoreReportList } from '../../../modules/intentIqAnalyticsAdapter.js'; -import {FIRST_PARTY_KEY, PREBID, VERSION} from '../../../libraries/intentIqConstants/intentIqConstants.js'; +import { FIRST_PARTY_KEY, PREBID, VERSION } from '../../../libraries/intentIqConstants/intentIqConstants.js'; import * as detectBrowserUtils from '../../../libraries/intentIqUtils/detectBrowserUtils.js'; -import {getReferrer, appendVrrefAndFui} from '../../../libraries/intentIqUtils/getRefferer.js'; +import { getReferrer, appendVrrefAndFui } from '../../../libraries/intentIqUtils/getRefferer.js'; import { gppDataHandler, uspDataHandler, gdprDataHandler } from '../../../src/consentHandler.js'; const partner = 10; @@ -47,8 +47,6 @@ const getUserConfigWithReportingServerAddress = () => [ 'params': { 'partner': partner, 'unpack': null, - 'manualWinReportEnabled': false, - 'reportingServerAddress': REPORT_SERVER_ADDRESS }, 'storage': { 'type': 'html5', @@ -91,6 +89,14 @@ const getWonRequest = () => ({ 'status': 'rendered' }); +const enableAnalyticWithSpecialOptions = (options) => { + iiqAnalyticsAnalyticsAdapter.disableAnalytics() + iiqAnalyticsAnalyticsAdapter.enableAnalytics({ + provider: 'iiqAnalytics', + options + }) +} + describe('IntentIQ tests all', function () { let logErrorStub; let getWindowSelfStub; @@ -140,8 +146,10 @@ describe('IntentIQ tests all', function () { }); it('should send POST request with payload in request body if reportMethod is POST', function () { + enableAnalyticWithSpecialOptions({ + reportMethod: 'POST' + }) const [userConfig] = getUserConfig(); - userConfig.params.reportMethod = 'POST'; const wonRequest = getWonRequest(); config.getConfig.restore(); @@ -188,7 +196,7 @@ describe('IntentIQ tests all', function () { it('IIQ Analytical Adapter bid win report', function () { localStorage.setItem(FIRST_PARTY_KEY, defaultData); - getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({href: 'http://localhost:9876'}); + getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876' }); const expectedVrref = getWindowLocationStub().href; events.emit(EVENTS.BID_WON, getWonRequest()); @@ -220,10 +228,10 @@ describe('IntentIQ tests all', function () { }); it('should include adType in payload when present in reportExternalWin event', function () { + enableAnalyticWithSpecialOptions({ manualWinReportEnabled: true }) getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876/' }); const externalWinEvent = { cpm: 1, currency: 'USD', adType: 'banner' }; const [userConfig] = getUserConfig(); - userConfig.params.manualWinReportEnabled = true; config.getConfig.restore(); sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns([userConfig]); @@ -352,7 +360,7 @@ describe('IntentIQ tests all', function () { localStorage.setItem(FIRST_PARTY_KEY, '{"pcid":"testpcid", "group": "B"}'); localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, '{"data":"testpcid"}'); expect(window[`intentIqAnalyticsAdapter_${partner}`].reportExternalWin).to.be.a('function'); - expect(window[`intentIqAnalyticsAdapter_${partner}`].reportExternalWin({cpm: 1, currency: 'USD'})).to.equal(false); + expect(window[`intentIqAnalyticsAdapter_${partner}`].reportExternalWin({ cpm: 1, currency: 'USD' })).to.equal(false); }); it('should return window.location.href when window.self === window.top', function () { @@ -427,6 +435,7 @@ describe('IntentIQ tests all', function () { config.getConfig.restore(); sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns(USERID_CONFIG_BROWSER); detectBrowserStub = sinon.stub(detectBrowserUtils, 'detectBrowser').returns('safari'); + enableAnalyticWithSpecialOptions({ reportingServerAddress: REPORT_SERVER_ADDRESS }) localStorage.setItem(FIRST_PARTY_KEY, defaultData); events.emit(EVENTS.BID_WON, getWonRequest()); @@ -502,7 +511,7 @@ describe('IntentIQ tests all', function () { const spdObject = { foo: 'bar', value: 42 }; const expectedSpdEncoded = encodeURIComponent(JSON.stringify(spdObject)); - localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify({...defaultData, spd: spdObject})); + localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify({ ...defaultData, spd: spdObject })); getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876/' }); events.emit(EVENTS.BID_WON, getWonRequest()); @@ -517,7 +526,7 @@ describe('IntentIQ tests all', function () { const spdObject = 'server provided data'; const expectedSpdEncoded = encodeURIComponent(spdObject); - localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify({...defaultData, spd: spdObject})); + localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify({ ...defaultData, spd: spdObject })); getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876/' }); events.emit(EVENTS.BID_WON, getWonRequest()); @@ -561,11 +570,13 @@ describe('IntentIQ tests all', function () { // provide recent auctionEnd with matching bid to enrich payload events.getEvents.restore(); sinon.stub(events, 'getEvents').returns([ - { eventType: 'auctionEnd', args: { - auctionId: 'auc-1', - adUnitCodes: ['ad-unit-1'], - bidsReceived: [{ bidder: 'pubmatic', adUnitCode: 'ad-unit-1', cpm: 1, currency: 'USD', originalCpm: 1, originalCurrency: 'USD', status: 'rendered' }] - }} + { + eventType: 'auctionEnd', args: { + auctionId: 'auc-1', + adUnitCodes: ['ad-unit-1'], + bidsReceived: [{ bidder: 'pubmatic', adUnitCode: 'ad-unit-1', cpm: 1, currency: 'USD', originalCpm: 1, originalCurrency: 'USD', status: 'rendered' }] + } + } ]); // trigger adapter to subscribe @@ -735,7 +746,7 @@ describe('IntentIQ tests all', function () { adUnitConfigTests.forEach(({ adUnitConfig, description, event, expectedPlacementId }) => { it(description, function () { const [userConfig] = getUserConfig(); - userConfig.params.adUnitConfig = adUnitConfig; + enableAnalyticWithSpecialOptions({ adUnitConfig }) config.getConfig.restore(); sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns([userConfig]);