Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libraries/intentIqConstants/intentIqConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const GVLID = "1323";
export const VERSION = 0.33;
export const PREBID = "pbjs";
export const HOURS_24 = 86400000;
export const HOURS_72 = HOURS_24 * 3;

export const INVALID_ID = "INVALID_ID";

Expand Down
33 changes: 19 additions & 14 deletions libraries/intentIqUtils/getRefferer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,25 @@ import { getWindowTop, logError, getWindowLocation, getWindowSelf } from '../../
* Determines if the script is running inside an iframe and retrieves the URL.
* @return {string} The encoded vrref value representing the relevant URL.
*/
export function getReferrer() {

export function getCurrentUrl() {
let url = '';
try {
const url = getWindowSelf() === getWindowTop()
? getWindowLocation().href
: getWindowTop().location.href;
if (getWindowSelf() === getWindowTop()) {
// top page
url = getWindowLocation().href || '';
} else {
// iframe
url = getWindowTop().location.href || '';
}

if (url.length >= 50) {
const { origin } = new URL(url);
return origin;
}
return new URL(url).origin;
};

return url;
} catch (error) {
// Handling access errors, such as cross-domain restrictions
logError(`Error accessing location: ${error}`);
return '';
}
Expand All @@ -31,12 +37,12 @@ export function getReferrer() {
* @return {string} The modified URL with appended `vrref` or `fui` parameters.
*/
export function appendVrrefAndFui(url, domainName) {
const fullUrl = encodeURIComponent(getReferrer());
const fullUrl = getCurrentUrl();
if (fullUrl) {
return (url += '&vrref=' + getRelevantRefferer(domainName, fullUrl));
}
url += '&fui=1'; // Full Url Issue
url += '&vrref=' + encodeURIComponent(domainName || '');
if (domainName) url += '&vrref=' + encodeURIComponent(domainName);
return url;
}

Expand All @@ -47,10 +53,9 @@ export function appendVrrefAndFui(url, domainName) {
* @return {string} The relevant referrer
*/
export function getRelevantRefferer(domainName, fullUrl) {
if (domainName && isDomainIncluded(fullUrl, domainName)) {
return fullUrl;
}
return domainName ? encodeURIComponent(domainName) : fullUrl;
return encodeURIComponent(
domainName && isDomainIncluded(fullUrl, domainName) ? fullUrl : (domainName || fullUrl)
);
}

/**
Expand All @@ -61,7 +66,7 @@ export function getRelevantRefferer(domainName, fullUrl) {
*/
export function isDomainIncluded(fullUrl, domainName) {
try {
return fullUrl.includes(domainName);
return new URL(fullUrl).hostname === domainName;
} catch (error) {
logError(`Invalid URL provided: ${error}`);
return false;
Expand Down
17 changes: 17 additions & 0 deletions libraries/intentIqUtils/getUnitPosition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export function getUnitPosition(pbjs, adUnitCode) {
const adUnits = pbjs?.adUnits;
if (!Array.isArray(adUnits) || !adUnitCode) return;

for (let i = 0; i < adUnits.length; i++) {
const adUnit = adUnits[i];
if (adUnit?.code !== adUnitCode) continue;

const mediaTypes = adUnit?.mediaTypes;
if (!mediaTypes || typeof mediaTypes !== 'object') return;

const firstKey = Object.keys(mediaTypes)[0];
const pos = mediaTypes[firstKey]?.pos;
Comment on lines +12 to +13
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use mediaType-specific pos for multi-format adUnits

This picks the first key in adUnit.mediaTypes to read pos. For multi-format adUnits (e.g., banner + video), object key order is not guaranteed to match the winning mediaType, so a video win can be reported with the banner pos (or undefined), corrupting analytics. Consider selecting mediaTypes[data.mediaType]?.pos (or mapping from adType) when the event includes a media type.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pos is defined at the adUnit level and is identical across mediaTypes, so reading it from any mediaTypes entry yields the same value. We don’t depend on key order for correctness in this case.


return typeof pos === 'number' ? pos : undefined;
}
}
36 changes: 33 additions & 3 deletions libraries/intentIqUtils/intentIqConfig.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,33 @@
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 = (reportEndpoint, gdprDetected) => reportEndpoint && typeof reportEndpoint === 'string' ? reportEndpoint : gdprDetected ? 'https://reports-gdpr.intentiq.com/report' : 'https://reports.intentiq.com/report'
const REGION_MAPPING = {
gdpr: true,
apac: true,
emea: true
};

function checkRegion(region) {
if (typeof region !== 'string') return '';
const lower = region.toLowerCase();
return REGION_MAPPING[lower] ? lower : '';
}

function buildServerAddress(baseName, region) {
const checkedRegion = checkRegion(region);
if (checkedRegion) return `https://${baseName}-${checkedRegion}.intentiq.com`;
return `https://${baseName}.intentiq.com`;
}

export const getIiqServerAddress = (configParams = {}) => {
if (typeof configParams?.iiqServerAddress === 'string') return configParams.iiqServerAddress;
return buildServerAddress('api', configParams.region);
Comment on lines +19 to +21
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve GDPR endpoint selection when region unset

This helper now builds API/reporting/sync base URLs solely from configParams.region, which means GDPR detection from CMP no longer influences endpoint choice. If a publisher previously relied on automatic GDPR routing (e.g., gdprString present but no explicit region configured), requests will now go to *.intentiq.com instead of *-gdpr.intentiq.com, changing behavior for existing integrations and potentially violating data-residency/consent expectations. Consider retaining the CMP-based GDPR fallback (or mapping gdprString → region) so default behavior doesn’t regress for EU traffic.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In our system, each partner account is provisioned for a specific region, and traffic should use the matching regional account. So region routing is intentional and explicit — the account must match the target region.

};

export const iiqPixelServerAddress = (configParams = {}) => {
if (typeof configParams?.iiqPixelServerAddress === 'string') return configParams.iiqPixelServerAddress;
return buildServerAddress('sync', configParams.region);
};

export const reportingServerAddress = (reportEndpoint, region) => {
if (reportEndpoint && typeof reportEndpoint === 'string') return reportEndpoint;
const host = buildServerAddress('reports', region);
return `${host}/report`;
};
10 changes: 6 additions & 4 deletions libraries/intentIqUtils/urlUtils.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export function appendSPData (url, firstPartyData) {
const spdParam = firstPartyData?.spd ? encodeURIComponent(typeof firstPartyData.spd === 'object' ? JSON.stringify(firstPartyData.spd) : firstPartyData.spd) : '';
url += spdParam ? '&spd=' + spdParam : '';
return url
export function appendSPData (url, partnerData) {
const spdParam = partnerData?.spd ? encodeURIComponent(typeof partnerData.spd === 'object' ? JSON.stringify(partnerData.spd) : partnerData.spd) : '';
if (!spdParam) {
return url;
}
return `${url}&spd=${spdParam}`;
};
37 changes: 25 additions & 12 deletions modules/intentIqAnalyticsAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { ajax } from '../src/ajax.js';
import { EVENTS } from '../src/constants.js';
import { detectBrowser } from '../libraries/intentIqUtils/detectBrowserUtils.js';
import { appendSPData } from '../libraries/intentIqUtils/urlUtils.js';
import { appendVrrefAndFui, getReferrer } from '../libraries/intentIqUtils/getRefferer.js';
import { appendVrrefAndFui, getCurrentUrl, getRelevantRefferer } from '../libraries/intentIqUtils/getRefferer.js';
import { getCmpData } from '../libraries/intentIqUtils/getCmpData.js';
import { getUnitPosition } from '../libraries/intentIqUtils/getUnitPosition.js';
import {
VERSION,
PREBID,
Expand All @@ -16,10 +17,12 @@ import { reportingServerAddress } from '../libraries/intentIqUtils/intentIqConfi
import { handleAdditionalParams } from '../libraries/intentIqUtils/handleAdditionalParams.js';
import { gamPredictionReport } from '../libraries/intentIqUtils/gamPredictionReport.js';
import { defineABTestingGroup } from '../libraries/intentIqUtils/defineABTestingGroupUtils.js';
import { getGlobal } from '../src/prebidGlobal.js';

const MODULE_NAME = 'iiqAnalytics';
const analyticsType = 'endpoint';
const prebidVersion = '$prebid.version$';
const pbjs = getGlobal();
export const REPORTER_ID = Date.now() + '_' + getRandom(0, 1000);
let globalName;
let identityGlobalName;
Expand Down Expand Up @@ -70,10 +73,7 @@ const PARAMS_NAMES = {
const DEFAULT_URL = 'https://reports.intentiq.com/report';

const getDataForDefineURL = () => {
const cmpData = getCmpData();
const gdprDetected = cmpData.gdprString;

return [iiqAnalyticsAnalyticsAdapter.initOptions.reportingServerAddress, gdprDetected];
return [iiqAnalyticsAnalyticsAdapter.initOptions.reportingServerAddress, iiqAnalyticsAnalyticsAdapter.initOptions.region];
};

const getDefaultInitOptions = () => {
Expand All @@ -92,7 +92,8 @@ const getDefaultInitOptions = () => {
abPercentage: null,
abTestUuid: null,
additionalParams: null,
reportingServerAddress: ''
reportingServerAddress: '',
region: ''
}
}

Expand Down Expand Up @@ -123,12 +124,13 @@ function initAdapterConfig(config) {

const options = config?.options || {}
iiqConfig = options
const { manualWinReportEnabled, gamPredictReporting, reportMethod, reportingServerAddress, adUnitConfig, partner, ABTestingConfigurationSource, browserBlackList, domainName, additionalParams } = options
const { manualWinReportEnabled, gamPredictReporting, reportMethod, reportingServerAddress, region, adUnitConfig, partner, ABTestingConfigurationSource, browserBlackList, domainName, additionalParams } = options
iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled =
manualWinReportEnabled || false;
iiqAnalyticsAnalyticsAdapter.initOptions.reportMethod = parseReportingMethod(reportMethod);
iiqAnalyticsAnalyticsAdapter.initOptions.gamPredictReporting = typeof gamPredictReporting === 'boolean' ? gamPredictReporting : false;
iiqAnalyticsAnalyticsAdapter.initOptions.reportingServerAddress = typeof reportingServerAddress === 'string' ? reportingServerAddress : '';
iiqAnalyticsAnalyticsAdapter.initOptions.region = typeof region === 'string' ? region : '';
iiqAnalyticsAnalyticsAdapter.initOptions.adUnitConfig = typeof adUnitConfig === 'number' ? adUnitConfig : 1;
iiqAnalyticsAnalyticsAdapter.initOptions.configSource = ABTestingConfigurationSource;
iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup = defineABTestingGroup(options);
Expand All @@ -155,7 +157,7 @@ function receivePartnerData() {
return false
}
iiqAnalyticsAnalyticsAdapter.initOptions.fpid = FPD
const { partnerData, clientsHints = '', actualABGroup } = window[identityGlobalName]
const { partnerData, clientHints = '', actualABGroup } = window[identityGlobalName]

if (partnerData) {
iiqAnalyticsAnalyticsAdapter.initOptions.dataIdsInitialized = true;
Expand All @@ -172,7 +174,7 @@ function receivePartnerData() {
if (actualABGroup) {
iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup = actualABGroup;
}
iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints = clientsHints;
iiqAnalyticsAnalyticsAdapter.initOptions.clientHints = clientHints;
} catch (e) {
logError(e);
return false;
Expand Down Expand Up @@ -268,9 +270,10 @@ function getRandom(start, end) {

export function preparePayload(data) {
const result = getDefaultDataObject();
const fullUrl = getCurrentUrl();
result[PARAMS_NAMES.partnerId] = iiqAnalyticsAnalyticsAdapter.initOptions.partner;
result[PARAMS_NAMES.prebidVersion] = prebidVersion;
result[PARAMS_NAMES.referrer] = getReferrer();
result[PARAMS_NAMES.referrer] = getRelevantRefferer(iiqAnalyticsAnalyticsAdapter.initOptions.domainName, fullUrl);
result[PARAMS_NAMES.terminationCause] = iiqAnalyticsAnalyticsAdapter.initOptions.terminationCause;
result[PARAMS_NAMES.clientType] = iiqAnalyticsAnalyticsAdapter.initOptions.clientType;
result[PARAMS_NAMES.siteId] = iiqAnalyticsAnalyticsAdapter.initOptions.siteId;
Expand Down Expand Up @@ -345,6 +348,15 @@ function prepareData(data, result) {
if (data.status) {
result.status = data.status;
}
if (data.size) {
result.size = data.size;
}
if (typeof data.pos === 'number') {
result.pos = data.pos;
} else if (data.adUnitCode) {
const pos = getUnitPosition(pbjs, data.adUnitCode);
if (typeof pos === 'number') result.pos = pos;
}

result.prebidAuctionId = data.auctionId || data.prebidAuctionId;

Expand Down Expand Up @@ -410,6 +422,7 @@ function getDefaultDataObject() {
function constructFullUrl(data) {
const report = [];
const reportMethod = iiqAnalyticsAnalyticsAdapter.initOptions.reportMethod;
const partnerData = window[identityGlobalName]?.partnerData;
const currentBrowserLowerCase = detectBrowser();
data = btoa(JSON.stringify(data));
report.push(data);
Expand All @@ -432,11 +445,11 @@ function constructFullUrl(data) {
'&source=' +
PREBID +
'&uh=' +
encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints) +
encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.clientHints) +
(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 = appendSPData(url, partnerData);
url = appendVrrefAndFui(url, iiqAnalyticsAnalyticsAdapter.initOptions.domainName);

if (reportMethod === 'POST') {
Expand Down
11 changes: 6 additions & 5 deletions modules/intentIqAnalyticsAdapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,8 @@ pbjs.enableAnalytics({
provider: 'iiqAnalytics',
options: {
partner: 1177538,
manualWinReportEnabled: false,
reportMethod: "GET",
adUnitConfig: 1,
ABTestingConfigurationSource: 'IIQServer',
domainName: "currentDomain.com",
gamPredictReporting: false
}
});
```
Expand Down Expand Up @@ -90,7 +87,9 @@ 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.
adType: 'banner' // Specifies the type of ad served
adType: 'banner', // Specifies the type of ad served,
size: '320x250', // Size of adUnit item,
pos: 0 // The following values are defined in the ORTB 2.5 spec
}
```

Expand All @@ -108,6 +107,8 @@ adType: 'banner' // Specifies the type of ad served
| status | String | Status of the impression. Leave empty or undefined if Prebid is not the bidding platform | rendered | No |
| placementId | String | Unique identifier of the ad unit on the webpage that showed this ad | div-1 | No |
| adType | String | Specifies the type of ad served. Possible values: “banner“, “video“, “native“, “audio“. | banner | No |
| size | String | Size of adUnit item | '320x250' | No |
| pos | number | The pos field specifies the position of the adUnit on the page according to the OpenRTB 2.5 specification | 0 | No |

To report the auction win, call the function as follows:

Expand Down
Loading
Loading