diff --git a/libraries/intentIqConstants/intentIqConstants.js b/libraries/intentIqConstants/intentIqConstants.ts similarity index 94% rename from libraries/intentIqConstants/intentIqConstants.js rename to libraries/intentIqConstants/intentIqConstants.ts index bfa8ae48d82..d65cb3d5346 100644 --- a/libraries/intentIqConstants/intentIqConstants.js +++ b/libraries/intentIqConstants/intentIqConstants.ts @@ -7,8 +7,8 @@ export const WITHOUT_IIQ = "B"; export const DEFAULT_PERCENTAGE = 95; export const CLIENT_HINTS_KEY = "_iiq_ch"; export const EMPTY = "EMPTY"; -export const GVLID = "1323"; -export const VERSION = 0.37; +export const GVLID = 1323; +export const VERSION = 0.38; export const PREBID = "pbjs"; export const HOURS_24 = 86400000; export const HOURS_72 = HOURS_24 * 3; diff --git a/libraries/intentIqUtils/chUtils.js b/libraries/intentIqUtils/chUtils.ts similarity index 80% rename from libraries/intentIqUtils/chUtils.js rename to libraries/intentIqUtils/chUtils.ts index 363b2fe2bb6..cf2b7b0397b 100644 --- a/libraries/intentIqUtils/chUtils.js +++ b/libraries/intentIqUtils/chUtils.ts @@ -1,4 +1,4 @@ -export function isCHSupported(nav) { +export function isCHSupported(nav?) { const n = nav ?? (typeof navigator !== 'undefined' ? navigator : undefined); return typeof n?.userAgentData?.getHighEntropyValues === 'function'; }; diff --git a/libraries/intentIqUtils/cryptionUtils.js b/libraries/intentIqUtils/cryptionUtils.ts similarity index 80% rename from libraries/intentIqUtils/cryptionUtils.js rename to libraries/intentIqUtils/cryptionUtils.ts index f0d01b3d502..d70dc8309f3 100644 --- a/libraries/intentIqUtils/cryptionUtils.js +++ b/libraries/intentIqUtils/cryptionUtils.ts @@ -5,7 +5,7 @@ * @param {number} [key=42] The XOR key (0–255) to use for encryption. * @returns {string} The encrypted text as a dot-separated string. */ -export function encryptData(plainText, key = 42) { +export function encryptData(plainText: string, key: number = 42): string { let out = ''; for (let i = 0; i < plainText.length; i++) { out += (plainText.charCodeAt(i) ^ key) + '.'; @@ -21,11 +21,11 @@ export function encryptData(plainText, key = 42) { * @param {number} [key=42] The XOR key (0–255) used for encryption. * @returns {string} The decrypted plaintext. */ -export function decryptData(encryptedText, key = 42) { +export function decryptData(encryptedText: string, key: number = 42): string { const parts = encryptedText.split('.'); let out = ''; for (let i = 0; i < parts.length; i++) { - out += String.fromCharCode(parts[i] ^ key); + out += String.fromCharCode(Number(parts[i]) ^ key); } return out; -} +} \ No newline at end of file diff --git a/libraries/intentIqUtils/defineABTestingGroupUtils.js b/libraries/intentIqUtils/defineABTestingGroupUtils.ts similarity index 74% rename from libraries/intentIqUtils/defineABTestingGroupUtils.js rename to libraries/intentIqUtils/defineABTestingGroupUtils.ts index af810ac278c..11fddb51ef1 100644 --- a/libraries/intentIqUtils/defineABTestingGroupUtils.js +++ b/libraries/intentIqUtils/defineABTestingGroupUtils.ts @@ -3,7 +3,15 @@ import { WITHOUT_IIQ, DEFAULT_PERCENTAGE, AB_CONFIG_SOURCE, -} from "../intentIqConstants/intentIqConstants.js"; +} from '../intentIqConstants/intentIqConstants.js'; + +type ABGroup = typeof WITH_IIQ | typeof WITHOUT_IIQ; + +interface ABTestingConfig { + ABTestingConfigurationSource?: string; + abPercentage?: number; + group?: string; +} /** * Fix percentage if provided some incorrect data @@ -11,7 +19,7 @@ import { * clampPct(-5) => 0 * clampPct('abc') => DEFAULT_PERCENTAGE */ -function clampPct(val) { +function clampPct(val: unknown): number { const n = Number(val); if (!Number.isFinite(n)) return DEFAULT_PERCENTAGE; // fallback = 95 return Math.max(0, Math.min(100, n)); @@ -24,16 +32,18 @@ function clampPct(val) { * @param {number} pct The percentage threshold (0–100). * @returns {string} Returns WITH_IIQ for Group A or WITHOUT_IIQ for Group B. */ -function pickABByPercentage(pct) { +function pickABByPercentage(pct?: number): ABGroup { const percentageToUse = - typeof pct === "number" ? pct : DEFAULT_PERCENTAGE; + typeof pct === 'number' ? pct : DEFAULT_PERCENTAGE; const percentage = clampPct(percentageToUse); const roll = Math.floor(Math.random() * 100) + 1; return roll <= percentage ? WITH_IIQ : WITHOUT_IIQ; // A : B } -function configurationSourceGroupInitialization(group) { - return typeof group === 'string' && group.toUpperCase() === WITHOUT_IIQ ? WITHOUT_IIQ : WITH_IIQ; +function configurationSourceGroupInitialization(group?: string): ABGroup { + return typeof group === 'string' && group.toUpperCase() === WITHOUT_IIQ + ? WITHOUT_IIQ + : WITH_IIQ; } /** @@ -47,23 +57,27 @@ function configurationSourceGroupInitialization(group) { * @param {number} [abPercentage] A/B percentage provided by partner. * @returns {string} The determined group: WITH_IIQ (A) or WITHOUT_IIQ (B). */ - -function IIQServerConfigurationSource(tc, abPercentage) { - if (typeof tc === "number" && Number.isFinite(tc)) { +function IIQServerConfigurationSource(tc?: number, abPercentage?: number): ABGroup { + if (typeof tc === 'number' && Number.isFinite(tc)) { return tc === 41 ? WITHOUT_IIQ : WITH_IIQ; } return pickABByPercentage(abPercentage); } -export function defineABTestingGroup(configObject, tc) { +export function defineABTestingGroup( + configObject: ABTestingConfig, + tc?: number +): ABGroup { switch (configObject.ABTestingConfigurationSource) { case AB_CONFIG_SOURCE.GROUP: return configurationSourceGroupInitialization( configObject.group ); + case AB_CONFIG_SOURCE.PERCENTAGE: return pickABByPercentage(configObject.abPercentage); + default: { if (!configObject.ABTestingConfigurationSource) { configObject.ABTestingConfigurationSource = AB_CONFIG_SOURCE.IIQ_SERVER; @@ -71,4 +85,4 @@ export function defineABTestingGroup(configObject, tc) { return IIQServerConfigurationSource(tc, configObject.abPercentage); } } -} +} \ No newline at end of file diff --git a/libraries/intentIqUtils/detectBrowserUtils.js b/libraries/intentIqUtils/detectBrowserUtils.ts similarity index 69% rename from libraries/intentIqUtils/detectBrowserUtils.js rename to libraries/intentIqUtils/detectBrowserUtils.ts index 37a935bda28..49a9385fb95 100644 --- a/libraries/intentIqUtils/detectBrowserUtils.js +++ b/libraries/intentIqUtils/detectBrowserUtils.ts @@ -1,15 +1,24 @@ import { logError } from '../../src/utils.js'; +type BrowserName = + | 'chrome' + | 'edge' + | 'firefox' + | 'ie' + | 'opera' + | 'safari' + | 'unknown'; + /** * Detects the browser using either userAgent or userAgentData * @return {string} The name of the detected browser or 'unknown' if unable to detect */ -export function detectBrowser() { +export function detectBrowser(): BrowserName { try { - if (navigator.userAgent) { + if (navigator?.userAgent) { return detectBrowserFromUserAgent(navigator.userAgent); - } else if (navigator.userAgentData) { - return detectBrowserFromUserAgentData(navigator.userAgentData); + } else if ((navigator as any)?.userAgentData) { + return detectBrowserFromUserAgentData((navigator as any)?.userAgentData); } } catch (error) { logError('Error detecting browser:', error); @@ -22,8 +31,8 @@ export function detectBrowser() { * @param {string} userAgent - The user agent string from the browser * @return {string} The name of the detected browser or 'unknown' if unable to detect */ -export function detectBrowserFromUserAgent(userAgent) { - const browserRegexPatterns = { +export function detectBrowserFromUserAgent(userAgent: string): BrowserName { + const browserRegexPatterns: Record = { opera: /Opera|OPR/, edge: /Edg/, chrome: /Chrome|CriOS/, @@ -48,14 +57,17 @@ export function detectBrowserFromUserAgent(userAgent) { } // Now we can safely check for Safari - if (browserRegexPatterns.safari.test(userAgent) && !browserRegexPatterns.chrome.test(userAgent)) { + if ( + browserRegexPatterns.safari.test(userAgent) && + !browserRegexPatterns.chrome.test(userAgent) + ) { return 'safari'; } // Check other browsers for (const browser in browserRegexPatterns) { if (browserRegexPatterns[browser].test(userAgent)) { - return browser; + return browser as BrowserName; } } @@ -67,16 +79,20 @@ export function detectBrowserFromUserAgent(userAgent) { * @param {Object} userAgentData - The user agent data object from the browser * @return {string} The name of the detected browser or 'unknown' if unable to detect */ -export function detectBrowserFromUserAgentData(userAgentData) { +export function detectBrowserFromUserAgentData( + userAgentData +): BrowserName { const brandNames = userAgentData.brands.map(brand => brand.brand); if (brandNames.includes('Microsoft Edge')) { return 'edge'; } else if (brandNames.includes('Opera')) { return 'opera'; - } else if (brandNames.some(brand => brand === 'Chromium' || brand === 'Google Chrome')) { + } else if ( + brandNames.some(brand => brand === 'Chromium' || brand === 'Google Chrome') + ) { return 'chrome'; } return 'unknown'; -} +} \ No newline at end of file diff --git a/libraries/intentIqUtils/gamPredictionReport.js b/libraries/intentIqUtils/gamPredictionReport.ts similarity index 56% rename from libraries/intentIqUtils/gamPredictionReport.js rename to libraries/intentIqUtils/gamPredictionReport.ts index bc5bcbac566..79f072c9350 100644 --- a/libraries/intentIqUtils/gamPredictionReport.js +++ b/libraries/intentIqUtils/gamPredictionReport.ts @@ -2,13 +2,17 @@ import { getEvents } from '../../src/events.js'; import { logError } from '../../src/utils.js'; import { getSlotTargetingMap } from '../../src/utils/gptTargeting.js'; -export function gamPredictionReport (gamObjectReference, sendData) { +export function gamPredictionReport( + gamObjectReference: any, + sendData: (data: Record) => void +): void { try { if (!gamObjectReference || !sendData) { logError('Failed to get gamPredictionReport, required data is missed'); return; } - const getSlotTargeting = (slot) => { + + const getSlotTargeting = (slot: any): Record => { try { return getSlotTargetingMap(slot); } catch (e) { @@ -17,11 +21,11 @@ export function gamPredictionReport (gamObjectReference, sendData) { } }; - const extractWinData = (gamEvent) => { + const extractWinData = (gamEvent: any): Record | undefined => { const slot = gamEvent.slot; const targeting = getSlotTargeting(slot); - const dataToSend = { + const dataToSend: Record = { placementId: slot.getSlotElementId && slot.getSlotElementId(), adUnitPath: slot.getAdUnitPath && slot.getAdUnitPath(), bidderCode: targeting.hb_bidder ? targeting.hb_bidder[0] : null, @@ -30,70 +34,88 @@ export function gamPredictionReport (gamObjectReference, sendData) { if (dataToSend.placementId) { // TODO check auto subscription to prebid events - const bidWonEvents = getEvents().filter((ev) => ev.eventType === 'bidWon'); + const bidWonEvents = getEvents().filter((ev: any) => 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 + targeting.hb_adid && + targeting.hb_adid[0] === (element.args as any).adId ) { - return; // don't send report if there was bidWon event earlier + return; } } } - const endEvents = getEvents().filter((ev) => ev.eventType === 'auctionEnd'); + + const endEvents = getEvents().filter((ev: any) => 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 + + if ((element.args as any)?.adUnitCodes?.includes(dataToSend.placementId)) { + const defineRelevantData = (bid: any): void => { + dataToSend.cpm = bid.cpm + 0.01; dataToSend.currency = bid.currency; dataToSend.originalCpm = bid.originalCpm; dataToSend.originalCurrency = bid.originalCurrency; dataToSend.status = bid.status; - dataToSend.prebidAuctionId = element.args?.auctionId; - if (!dataToSend.bidderCode) dataToSend.bidderCode = 'GAM'; + dataToSend.prebidAuctionId = (element.args as any)?.auctionId; + + if (!dataToSend.bidderCode) { + dataToSend.bidderCode = 'GAM'; + } }; + if (dataToSend.bidderCode) { - const relevantBid = element.args?.bidsReceived.find( - (item) => + const relevantBid = (element.args as any)?.bidsReceived.find( + (item: any) => item.bidder === dataToSend.bidderCode && - item.adUnitCode === dataToSend.placementId + 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) { + + (element.args as any)?.bidsReceived.forEach((bid: any) => { + 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) { - sendData(data); + gamObjectReference.pubads().addEventListener( + 'slotRenderEnded', + (event: any) => { + if (event.isEmpty) return; + + const data = extractWinData(event); + if (data) { + sendData(data); + } } - }); + ); }); } catch (error) { logError('Failed to subscribe to GAM: ' + error); } -}; +} \ No newline at end of file diff --git a/libraries/intentIqUtils/getCmpData.js b/libraries/intentIqUtils/getCmpData.ts similarity index 56% rename from libraries/intentIqUtils/getCmpData.js rename to libraries/intentIqUtils/getCmpData.ts index 63a953b77c9..3e062219ca8 100644 --- a/libraries/intentIqUtils/getCmpData.js +++ b/libraries/intentIqUtils/getCmpData.ts @@ -1,4 +1,12 @@ -import { allConsent } from "../../src/consentHandler.js"; +import { allConsent } from '../../src/consentHandler.js'; + +interface CmpData { + gdprApplies: boolean; + gdprString: string | null; + uspString: string | null; + gppString: string | null; + tcfApiVersion?: number | string; +} /** * Retrieves consent data from the Consent Management Platform (CMP). @@ -8,26 +16,34 @@ import { allConsent } from "../../src/consentHandler.js"; * - `uspString` (string): USP consent string if available. * - `gppString` (string): GPP consent string if available. */ -export function getCmpData() { +export function getCmpData(): CmpData { const consentData = allConsent.getConsentData(); return { gdprApplies: consentData?.gdpr?.gdprApplies || false, - gdprString: typeof consentData?.gdpr?.consentString === 'string' ? consentData.gdpr.consentString : null, - uspString: typeof consentData?.usp === 'string' ? consentData.usp : null, - gppString: typeof consentData?.gpp?.gppString === 'string' ? consentData.gpp.gppString : null, + gdprString: typeof consentData?.gdpr?.consentString === 'string' + ? consentData.gdpr.consentString + : null, + uspString: typeof consentData?.usp === 'string' + ? consentData.usp + : null, + gppString: typeof consentData?.gpp?.gppString === 'string' + ? consentData.gpp.gppString + : null, tcfApiVersion: consentData?.gdpr?.apiVersion }; } -export function isValidValue(val) { +export function isValidValue(val: unknown): boolean { return !!val && val !== 'undefined'; } -export function areCmpValuesEqual(a, b) { +export function areCmpValuesEqual(a: unknown, b: unknown): boolean { const aValid = isValidValue(a); const bValid = isValidValue(b); + if (!aValid && !bValid) return true; if (aValid !== bValid) return false; + return a === b; -} +} \ No newline at end of file diff --git a/libraries/intentIqUtils/getRefferer.js b/libraries/intentIqUtils/getRefferer.ts similarity index 77% rename from libraries/intentIqUtils/getRefferer.js rename to libraries/intentIqUtils/getRefferer.ts index 40fdabb2b0b..20a68607f3a 100644 --- a/libraries/intentIqUtils/getRefferer.js +++ b/libraries/intentIqUtils/getRefferer.ts @@ -4,9 +4,9 @@ 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 getCurrentUrl(): string { + let url: string; -export function getCurrentUrl() { - let url; try { if (getWindowSelf() === getWindowTop()) { // top page @@ -18,7 +18,7 @@ export function getCurrentUrl() { if (url.length >= 50) { return new URL(url).origin; - }; + } return url; } catch (error) { @@ -36,13 +36,19 @@ export function getCurrentUrl() { * @param {string} domainName - The domain name used to determine the relevant referrer. * @return {string} The modified URL with appended `vrref` or `fui` parameters. */ -export function appendVrrefAndFui(url, domainName) { +export function appendVrrefAndFui(url: string, domainName?: string): string { const fullUrl = getCurrentUrl(); + if (fullUrl) { - return (url + '&vrref=' + getRelevantRefferer(domainName, fullUrl)); + return url + '&vrref=' + getRelevantRefferer(domainName, fullUrl); } + url += '&fui=1'; // Full Url Issue - if (domainName) url += '&vrref=' + encodeURIComponent(domainName); + + if (domainName) { + url += '&vrref=' + encodeURIComponent(domainName); + } + return url; } @@ -52,9 +58,11 @@ export function appendVrrefAndFui(url, domainName) { * @param {string} fullUrl The full URL to analyze * @return {string} The relevant referrer */ -export function getRelevantRefferer(domainName, fullUrl) { +export function getRelevantRefferer(domainName: string | undefined, fullUrl: string): string { return encodeURIComponent( - domainName && isDomainIncluded(fullUrl, domainName) ? fullUrl : (domainName || fullUrl) + domainName && isDomainIncluded(fullUrl, domainName) + ? fullUrl + : (domainName || fullUrl) ); } @@ -64,11 +72,11 @@ export function getRelevantRefferer(domainName, fullUrl) { * @param {string} domainName - The domain name to search for within the URL. * @return {boolean} `True` if the domain name is found in the URL, `false` otherwise. */ -export function isDomainIncluded(fullUrl, domainName) { +export function isDomainIncluded(fullUrl: string, domainName: string): boolean { try { return new URL(fullUrl).hostname === domainName; } catch (error) { logError(`Invalid URL provided: ${error}`); return false; } -} +} \ No newline at end of file diff --git a/libraries/intentIqUtils/getSyncKey.js b/libraries/intentIqUtils/getSyncKey.js deleted file mode 100644 index a21dd140e4c..00000000000 --- a/libraries/intentIqUtils/getSyncKey.js +++ /dev/null @@ -1 +0,0 @@ -export const SYNC_KEY = (partner) => '_iiq_sync' + '_' + partner; diff --git a/libraries/intentIqUtils/getSyncKey.ts b/libraries/intentIqUtils/getSyncKey.ts new file mode 100644 index 00000000000..d3f74da48f2 --- /dev/null +++ b/libraries/intentIqUtils/getSyncKey.ts @@ -0,0 +1 @@ +export const SYNC_KEY = (partner: number): string => `_iiq_sync_${partner}`; \ No newline at end of file diff --git a/libraries/intentIqUtils/getUnitPosition.js b/libraries/intentIqUtils/getUnitPosition.ts similarity index 62% rename from libraries/intentIqUtils/getUnitPosition.js rename to libraries/intentIqUtils/getUnitPosition.ts index 025a06a9964..976794b6ed7 100644 --- a/libraries/intentIqUtils/getUnitPosition.js +++ b/libraries/intentIqUtils/getUnitPosition.ts @@ -1,4 +1,14 @@ -export function getUnitPosition(pbjs, adUnitCode) { +interface Pbjs { + adUnits?: Array<{ + code?: string; + mediaTypes?: Record; + }>; +} + +export function getUnitPosition( + pbjs: Pbjs | undefined, + adUnitCode: string +): number | undefined { const adUnits = pbjs?.adUnits; if (!Array.isArray(adUnits) || !adUnitCode) return; @@ -6,7 +16,7 @@ export function getUnitPosition(pbjs, adUnitCode) { const adUnit = adUnits[i]; if (adUnit?.code !== adUnitCode) continue; - const mediaTypes = adUnit?.mediaTypes; + const mediaTypes = adUnit.mediaTypes; if (!mediaTypes || typeof mediaTypes !== 'object') return; const firstKey = Object.keys(mediaTypes)[0]; @@ -14,4 +24,4 @@ export function getUnitPosition(pbjs, adUnitCode) { return typeof pos === 'number' ? pos : undefined; } -} +} \ No newline at end of file diff --git a/libraries/intentIqUtils/handleAdditionalParams.js b/libraries/intentIqUtils/handleAdditionalParams.ts similarity index 100% rename from libraries/intentIqUtils/handleAdditionalParams.js rename to libraries/intentIqUtils/handleAdditionalParams.ts diff --git a/libraries/intentIqUtils/intentIqConfig.js b/libraries/intentIqUtils/intentIqConfig.js deleted file mode 100644 index 41c731f646b..00000000000 --- a/libraries/intentIqUtils/intentIqConfig.js +++ /dev/null @@ -1,33 +0,0 @@ -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); -}; - -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`; -}; diff --git a/libraries/intentIqUtils/intentIqConfig.ts b/libraries/intentIqUtils/intentIqConfig.ts new file mode 100644 index 00000000000..8b4c9ed04f4 --- /dev/null +++ b/libraries/intentIqUtils/intentIqConfig.ts @@ -0,0 +1,59 @@ +const REGION_MAPPING: Record = { + gdpr: true, + apac: true, + emea: true +}; + +interface ServerConfig { + iiqServerAddress?: string; + iiqPixelServerAddress?: string; + region?: string; +} + +function checkRegion(region?: string): string { + if (typeof region !== 'string') return ''; + const lower = region.toLowerCase(); + return REGION_MAPPING[lower] ? lower : ''; +} + +function buildServerAddress(baseName: string, region?: string): string { + const checkedRegion = checkRegion(region); + + if (checkedRegion) { + return `https://${baseName}-${checkedRegion}.intentiq.com`; + } + + return `https://${baseName}.intentiq.com`; +} + +export const getIiqServerAddress = ( + configParams: ServerConfig = {} +): string => { + if (typeof configParams.iiqServerAddress === 'string') { + return configParams.iiqServerAddress; + } + + return buildServerAddress('api', configParams.region); +}; + +export const iiqPixelServerAddress = ( + configParams: ServerConfig = {} +): string => { + if (typeof configParams.iiqPixelServerAddress === 'string') { + return configParams.iiqPixelServerAddress; + } + + return buildServerAddress('sync', configParams.region); +}; + +export const reportingServerAddress = ( + reportEndpoint?: string, + region?: string +): string => { + if (typeof reportEndpoint === 'string') { + return reportEndpoint; + } + + const host = buildServerAddress('reports', region); + return `${host}/report`; +}; \ No newline at end of file diff --git a/libraries/intentIqUtils/storageUtils.js b/libraries/intentIqUtils/storageUtils.ts similarity index 70% rename from libraries/intentIqUtils/storageUtils.js rename to libraries/intentIqUtils/storageUtils.ts index dd94ad895de..3d0a90baa39 100644 --- a/libraries/intentIqUtils/storageUtils.js +++ b/libraries/intentIqUtils/storageUtils.ts @@ -1,11 +1,23 @@ import { logError, logInfo } from '../../src/utils.js'; -import { SUPPORTED_TYPES, FIRST_PARTY_KEY } from '../../libraries/intentIqConstants/intentIqConstants.js'; +import { SUPPORTED_TYPES, FIRST_PARTY_KEY } from '../intentIqConstants/intentIqConstants.js'; import { getStorageManager } from '../../src/storageManager.js'; import { MODULE_TYPE_UID } from '../../src/activities/modules.js'; const MODULE_NAME = 'intentIqId'; const PCID_EXPIRY = 365; +export type AllowedStorageType = 'html5' | 'cookie'; + +interface FirstPartyData { + isOptedOut?: boolean; + pcid?: string; + pcidDate?: number; + pid?: string; + abTestUuid?: string; + terminationCause?: number; + [key: string]: unknown; +} + export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); /** @@ -13,7 +25,7 @@ export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleNa * @param {string} key * @returns {boolean} */ -export function isPartnerDataKey(key) { +export function isPartnerDataKey(key: string): boolean { if (typeof key !== 'string') return false; const parts = key.split('_fdata_'); if (parts.length < 2) return false; @@ -27,7 +39,10 @@ export function isPartnerDataKey(key) { * @param {Array} allowedStorage - Array of allowed storage types ('html5' or 'cookie'). * @return {string|null} - The retrieved data or null if an error occurs. */ -export function readData(key, allowedStorage) { +export function readData( + key: string, + allowedStorage: AllowedStorageType[] +): string | null { try { if (storage.hasLocalStorage() && allowedStorage.includes('html5')) { return storage.getDataFromLocalStorage(key); @@ -49,7 +64,12 @@ export function readData(key, allowedStorage) { * @param {Array} allowedStorage - An array of allowed storage types: 'html5' for Local Storage and/or 'cookie' for Cookies. * @param {Object} firstPartyData - Contains user consent data; when isOptedOut is true only a stripped subset is persisted to device. */ -export function storeData(key, value, allowedStorage, firstPartyData) { +export function storeData( + key: string, + value: string, + allowedStorage: AllowedStorageType[], + firstPartyData?: FirstPartyData +): void { try { if (firstPartyData?.isOptedOut) { // Limit what reaches device storage when the user is opted out. @@ -57,7 +77,13 @@ export function storeData(key, value, allowedStorage, firstPartyData) { // - Partner data (_iiq_fdata_): persist only terminationCause. // - Anything else: do not persist. if (key === FIRST_PARTY_KEY) { - const parsed = typeof value === 'string' ? tryParse(value) : (value && typeof value === 'object' ? { ...value } : null); + const parsed = + typeof value === 'string' + ? tryParse(value) + : (value && typeof value === 'object' + ? { ...(value as FirstPartyData) } + : null); + if (parsed) { delete parsed.pcid; delete parsed.pcidDate; @@ -66,19 +92,33 @@ export function storeData(key, value, allowedStorage, firstPartyData) { value = JSON.stringify(parsed); } } else if (isPartnerDataKey(key)) { - const parsed = typeof value === 'string' ? tryParse(value) : (value && typeof value === 'object' ? value : null); - value = JSON.stringify({ terminationCause: parsed ? parsed.terminationCause : undefined }); + const parsed = + typeof value === 'string' + ? tryParse(value) + : (value && typeof value === 'object' + ? (value as FirstPartyData) + : null); + + value = JSON.stringify({ + terminationCause: parsed ? parsed.terminationCause : undefined + }); } else { return; } } + logInfo(MODULE_NAME + ': storing data: key=' + key + ' value=' + value); + if (value) { if (storage.hasLocalStorage() && allowedStorage.includes('html5')) { storage.setDataInLocalStorage(key, value); } + if (storage.cookiesAreEnabled() && allowedStorage.includes('cookie')) { - const expiresStr = (new Date(Date.now() + (PCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString(); + const expiresStr = new Date( + Date.now() + (PCID_EXPIRY * (60 * 60 * 24 * 1000)) + ).toUTCString(); + storage.setCookie(key, value, expiresStr, 'LAX'); } } @@ -91,12 +131,15 @@ export function storeData(key, value, allowedStorage, firstPartyData) { * Remove Intent IQ data from cookie or local storage * @param key */ - -export function removeDataByKey(key, allowedStorage) { +export function removeDataByKey( + key: string, + allowedStorage: AllowedStorageType[] +): void { try { if (storage.hasLocalStorage() && allowedStorage.includes('html5')) { storage.removeDataFromLocalStorage(key); } + if (storage.cookiesAreEnabled() && allowedStorage.includes('cookie')) { const expiredDate = new Date(0).toUTCString(); storage.setCookie(key, '', expiredDate, 'LAX'); @@ -113,9 +156,15 @@ export function removeDataByKey(key, allowedStorage) { * @param {Array} params - An array containing storage type preferences, e.g., ['html5', 'cookie']. * @return {Array} - Returns an array with allowed storage types. Defaults to ['html5'] if no valid options are provided. */ -export function defineStorageType(params) { +export function defineStorageType( + params?: string[] +): AllowedStorageType[] { if (!params || !Array.isArray(params)) return ['html5']; // use locale storage be default - const filteredArr = params.filter(item => SUPPORTED_TYPES.includes(item)); + + const filteredArr = params.filter( + (item): item is AllowedStorageType => SUPPORTED_TYPES.includes(item) + ); + return filteredArr.length ? filteredArr : ['html5']; } @@ -123,11 +172,11 @@ export function defineStorageType(params) { * Parse json if possible, else return null * @param data */ -export function tryParse(data) { +export function tryParse(data: string): T | null { try { - return JSON.parse(data); + return JSON.parse(data) as T; } catch (err) { logError(err); return null; } -} +} \ No newline at end of file diff --git a/libraries/intentIqUtils/urlUtils.js b/libraries/intentIqUtils/urlUtils.js deleted file mode 100644 index 78579aeb67d..00000000000 --- a/libraries/intentIqUtils/urlUtils.js +++ /dev/null @@ -1,7 +0,0 @@ -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}`; -}; diff --git a/libraries/intentIqUtils/urlUtils.ts b/libraries/intentIqUtils/urlUtils.ts new file mode 100644 index 00000000000..b1a815597fc --- /dev/null +++ b/libraries/intentIqUtils/urlUtils.ts @@ -0,0 +1,22 @@ +interface PartnerData { + spd?: string | Record; +} + +export function appendSPData( + url: string, + partnerData?: PartnerData +): string { + const spdParam = partnerData?.spd + ? encodeURIComponent( + typeof partnerData.spd === 'object' + ? JSON.stringify(partnerData.spd) + : partnerData.spd + ) + : ''; + + if (!spdParam) { + return url; + } + + return `${url}&spd=${spdParam}`; +} \ No newline at end of file diff --git a/modules/adtelligentIdSystem.js b/modules/adtelligentIdSystem.js index b2921f5874e..aee0329e36f 100644 --- a/modules/adtelligentIdSystem.js +++ b/modules/adtelligentIdSystem.js @@ -9,13 +9,6 @@ import { qualifiedAjaxBuilder } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { MODULE_TYPE_UID } from '../src/activities/modules.js'; -/** - * @typedef {import('../modules/userId/index.js').Submodule} Submodule - * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig - * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData - * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse - */ - const gvlid = 410; const moduleName = 'adtelligent'; const syncUrl = 'https://idrs.adtelligent.com/get'; diff --git a/modules/intentIqAnalyticsAdapter.d.ts b/modules/intentIqAnalyticsAdapter.d.ts deleted file mode 100644 index 0c2017ea6bc..00000000000 --- a/modules/intentIqAnalyticsAdapter.d.ts +++ /dev/null @@ -1,152 +0,0 @@ -import type { IntentIqABConfigSource } from './intentIqIdSystem'; - -/** - * Payload passed to `window.intentIqAnalyticsAdapter_.reportExternalWin()`. - * Use this when Prebid is NOT the winning bidding platform (e.g. Amazon TAM, GAM). - */ -export interface IiqExternalWinData { - /** - * Platform that rendered this impression. - * 1 = Prebid, 2 = Amazon, 3 = Google, 4 = Open RTB / local Prebid server. - */ - biddingPlatformId: 1 | 2 | 3 | 4; - - /** - * Unified auction identifier when running multiple auction solutions. - */ - partnerAuctionId?: string; - - /** - * Name of the bidder that won the auction as reported by the platform. - */ - bidderCode: string; - - /** - * Prebid auction ID. Leave undefined when Prebid is not the platform. - */ - prebidAuctionId?: string; - - /** - * CPM received from the demand-side auction, before any floor adjustments. - */ - cpm: number; - - /** - * ISO 4217 currency code for `cpm`, e.g. `'USD'`. - */ - currency: string; - - /** - * Pre-adjustment CPM. Leave undefined when Prebid is not the platform. - */ - originalCpm?: number; - - /** - * Currency of `originalCpm`. Leave undefined when Prebid is not the platform. - */ - originalCurrency?: string; - - /** - * Impression status. Leave undefined when Prebid is not the platform. - */ - status?: string; - - /** - * Unique identifier of the ad unit that showed this ad. - */ - placementId?: string; - - /** - * Type of ad served. - */ - adType?: 'banner' | 'video' | 'native' | 'audio'; -} - -/** - * Options passed to `pbjs.enableAnalytics({ provider: 'iiqAnalytics', options: { … } })`. - */ -export interface IntentIqAnalyticsAdapterOptions { - /** - * Partner ID assigned by IntentIQ. Required. - */ - partner: number; - - /** - * Set to `true` to allow manual win reporting via - * `window.intentIqAnalyticsAdapter_.reportExternalWin()`. - * Defaults to `false`. - */ - manualWinReportEnabled?: boolean; - - /** - * Enable GAM predict-score reporting. Defaults to `false`. - */ - gamPredictReporting?: boolean; - - /** - * HTTP method used to send reports. Defaults to `'GET'`. - */ - reportMethod?: 'GET' | 'POST'; - - /** - * Override for the IntentIQ reporting server base URL. - */ - reportingServerAddress?: string; - - /** - * Geo-region routing hint for the reporting server. - */ - region?: string; - - /** - * Controls how the `placementId` field in reports is populated: - * 1 = adUnitCode then placementId (default) - * 2 = placementId then adUnitCode - * 3 = adUnitCode only - * 4 = placementId only - */ - adUnitConfig?: 1 | 2 | 3 | 4; - - /** - * Determines how the A/B test group is assigned. Defaults to `'IIQServer'`. - */ - ABTestingConfigurationSource?: IntentIqABConfigSource; - - /** - * Explicit A/B group override. Only used when - * `ABTestingConfigurationSource` is `'group'`. - */ - group?: 'A' | 'B'; - - /** - * Percentage of users placed in the WITH_IIQ cohort (0–100). Defaults to 95. - */ - abPercentage?: number; - - /** - * Comma-separated list of browser names (lowercase) excluded from reporting, - * e.g. `'chrome,safari'`. - */ - browserBlackList?: string; - - /** - * Publisher domain name appended to report URLs. - */ - domainName?: string; - - /** - * Freeform key-value pairs appended to every report URL. - */ - additionalParams?: Record; - - /** - * When `true`, first-party data is stored under a partner-specific key. - */ - siloEnabled?: boolean; - - /** - * Reference to the GAM `googletag.pubads()` object for predict-score - * reporting. - */ - gamObjectReference?: Record; -} diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.ts similarity index 73% rename from modules/intentIqAnalyticsAdapter.js rename to modules/intentIqAnalyticsAdapter.ts index 49875d61ab4..024e60b05ee 100644 --- a/modules/intentIqAnalyticsAdapter.js +++ b/modules/intentIqAnalyticsAdapter.ts @@ -18,20 +18,172 @@ import { handleAdditionalParams } from '../libraries/intentIqUtils/handleAdditio import { gamPredictionReport } from '../libraries/intentIqUtils/gamPredictionReport.js'; import { defineABTestingGroup } from '../libraries/intentIqUtils/defineABTestingGroupUtils.js'; import { getGlobal } from '../src/prebidGlobal.js'; +import { IntentIqABConfigSource } from './intentIqIdSystem.js'; + +/** + * Payload passed to `window.intentIqAnalyticsAdapter_.reportExternalWin()`. + * Use this when Prebid is NOT the winning bidding platform (e.g. Amazon TAM, GAM). + */ +export interface IiqExternalWinData { + /** + * Platform that rendered this impression. + * 1 = Prebid, 2 = Amazon, 3 = Google, 4 = Open RTB / local Prebid server. + */ + biddingPlatformId: 1 | 2 | 3 | 4; + + /** + * Unified auction identifier when running multiple auction solutions. + */ + partnerAuctionId?: string; + + /** + * Name of the bidder that won the auction as reported by the platform. + */ + bidderCode: string; + + /** + * Prebid auction ID. Leave undefined when Prebid is not the platform. + */ + prebidAuctionId?: string; + + /** + * CPM received from the demand-side auction, before any floor adjustments. + */ + cpm: number; + + /** + * ISO 4217 currency code for `cpm`, e.g. `'USD'`. + */ + currency: string; + + /** + * Pre-adjustment CPM. Leave undefined when Prebid is not the platform. + */ + originalCpm?: number; + + /** + * Currency of `originalCpm`. Leave undefined when Prebid is not the platform. + */ + originalCurrency?: string; + + /** + * Impression status. Leave undefined when Prebid is not the platform. + */ + status?: string; + + /** + * Unique identifier of the ad unit that showed this ad. + */ + placementId?: string; + + /** + * Type of ad served. + */ + adType?: 'banner' | 'video' | 'native' | 'audio'; +} + +/** + * Options passed to `pbjs.enableAnalytics({ provider: 'iiqAnalytics', options: { … } })`. + */ +export interface IntentIqAnalyticsAdapterOptions { + /** + * Partner ID assigned by IntentIQ. Required. + */ + partner: number; + + /** + * Set to `true` to allow manual win reporting via + * `window.intentIqAnalyticsAdapter_.reportExternalWin()`. + * Defaults to `false`. + */ + manualWinReportEnabled?: boolean; + + /** + * Enable GAM predict-score reporting. Defaults to `false`. + */ + gamPredictReporting?: boolean; + + /** + * HTTP method used to send reports. Defaults to `'GET'`. + */ + reportMethod?: 'GET' | 'POST'; + + /** + * Override for the IntentIQ reporting server base URL. + */ + reportingServerAddress?: string; + + /** + * Geo-region routing hint for the reporting server. + */ + region?: string; + + /** + * Controls how the `placementId` field in reports is populated: + * 1 = adUnitCode then placementId (default) + * 2 = placementId then adUnitCode + * 3 = adUnitCode only + * 4 = placementId only + */ + adUnitConfig?: 1 | 2 | 3 | 4; + + /** + * Determines how the A/B test group is assigned. Defaults to `'IIQServer'`. + */ + ABTestingConfigurationSource?: IntentIqABConfigSource; + + /** + * Explicit A/B group override. Only used when + * `ABTestingConfigurationSource` is `'group'`. + */ + group?: 'A' | 'B'; + + /** + * Percentage of users placed in the WITH_IIQ cohort (0–100). Defaults to 95. + */ + abPercentage?: number; + + /** + * Comma-separated list of browser names (lowercase) excluded from reporting, + * e.g. `'chrome,safari'`. + */ + browserBlackList?: string; + + /** + * Publisher domain name appended to report URLs. + */ + domainName?: string; + + /** + * Freeform key-value pairs appended to every report URL. + */ + additionalParams?: Record; + + /** + * When `true`, first-party data is stored under a partner-specific key. + */ + siloEnabled?: boolean; + + /** + * Reference to the GAM `googletag.pubads()` object for predict-score + * reporting. + */ + gamObjectReference?: Record; +} -const MODULE_NAME = 'iiqAnalytics'; -const analyticsType = 'endpoint'; +const MODULE_NAME = 'iiqAnalytics' as const; +const analyticsType = 'endpoint' as const; const prebidVersion = '$prebid.version$'; -const pbjs = getGlobal(); +const pbjs: any = getGlobal(); export const REPORTER_ID = Date.now() + '_' + getRandom(0, 1000); -let globalName; -let identityGlobalName; +let globalName: string | undefined; +let identityGlobalName: string | undefined; let alreadySubscribedOnGAM = false; -let reportList = {}; -let cleanReportsID; -let iiqConfig; +let reportList: Record> = {}; +let cleanReportsID: ReturnType | undefined; +let iiqConfig: any; -const PARAMS_NAMES = { +const PARAMS_NAMES: Record = { abTestGroup: 'abGroup', pbPauseUntil: 'pbPauseUntil', pbMonitoringEnabled: 'pbMonitoringEnabled', @@ -100,9 +252,9 @@ const getDefaultInitOptions = () => { }; }; -const iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ url: DEFAULT_URL, analyticsType }), { +const iiqAnalyticsAnalyticsAdapter: any = Object.assign(adapter({ url: DEFAULT_URL, analyticsType }), { initOptions: getDefaultInitOptions(), - track({ eventType, args }) { + track({ eventType, args }: { eventType: string; args: any }) { switch (eventType) { case BID_WON: bidWon(args); @@ -112,10 +264,10 @@ const iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ url: DEFAULT_URL, a alreadySubscribedOnGAM = true; gamPredictionReport(iiqConfig?.gamObjectReference, bidWon); } - const fpdFromGlobalObject = window[identityGlobalName]?.firstPartyData; + const fpdFromGlobalObject = (window as any)[identityGlobalName as string]?.firstPartyData; if (fpdFromGlobalObject) { const currentCmpData = getCmpData(); - const hasCmpMismatch = ['gdprString', 'gppString', 'uspString'].some(field => + const hasCmpMismatch = ['gdprString', 'gppString', 'uspString'].some((field: string) => !areCmpValuesEqual(fpdFromGlobalObject[field], currentCmpData[field]) ); if (hasCmpMismatch) { @@ -133,7 +285,7 @@ const iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ url: DEFAULT_URL, a // Events needed const { BID_WON, BID_REQUESTED } = EVENTS; -function initAdapterConfig(config) { +function initAdapterConfig(config: any): void { if (iiqAnalyticsAnalyticsAdapter.initOptions.adapterConfigInitialized) return; const options = config?.options || {}; @@ -163,15 +315,15 @@ function initAdapterConfig(config) { iiqAnalyticsAnalyticsAdapter.initOptions.adapterConfigInitialized = true; } -function receivePartnerData() { +function receivePartnerData(): boolean | void { try { iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs = null; - const FPD = window[identityGlobalName]?.firstPartyData; - if (!window[identityGlobalName] || !FPD) { + const FPD = (window as any)[identityGlobalName as string]?.firstPartyData; + if (!(window as any)[identityGlobalName as string] || !FPD) { return false; } iiqAnalyticsAnalyticsAdapter.initOptions.fpid = FPD; - const { partnerData, clientHints = '', actualABGroup } = window[identityGlobalName]; + const { partnerData, clientHints = '', actualABGroup } = (window as any)[identityGlobalName as string]; if (partnerData) { iiqAnalyticsAnalyticsAdapter.initOptions.dataIdsInitialized = true; @@ -190,7 +342,7 @@ function receivePartnerData() { } iiqAnalyticsAnalyticsAdapter.initOptions.clientHints = clientHints; - const { abPercentage, userProvidedAbPercentage } = window[identityGlobalName]; + const { abPercentage, userProvidedAbPercentage } = (window as any)[identityGlobalName as string]; if (abPercentage !== undefined) { iiqAnalyticsAnalyticsAdapter.initOptions.abPercentage = abPercentage; } @@ -201,9 +353,9 @@ function receivePartnerData() { } } -function shouldSubscribeOnGAM() { +function shouldSubscribeOnGAM(): boolean { if (!iiqConfig?.gamObjectReference || !isPlainObject(iiqConfig.gamObjectReference)) return false; - const partnerData = window[identityGlobalName]?.partnerData; + const partnerData = (window as any)[identityGlobalName as string]?.partnerData; if (partnerData) { return partnerData.gpr || (!('gpr' in partnerData) && iiqAnalyticsAnalyticsAdapter.initOptions.gamPredictReporting); @@ -211,7 +363,7 @@ function shouldSubscribeOnGAM() { return false; } -function shouldSendReport(isReportExternal) { +function shouldSendReport(isReportExternal?: boolean): boolean { return ( (isReportExternal && iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled && @@ -224,7 +376,7 @@ export function restoreReportList() { reportList = {}; } -function bidWon(args, isReportExternal) { +function bidWon(args: any, isReportExternal?: boolean): boolean | void { if ( isNaN(iiqAnalyticsAnalyticsAdapter.initOptions.partner) ) { @@ -258,7 +410,7 @@ function bidWon(args, isReportExternal) { return false; } -function parseReportingMethod(reportMethod) { +function parseReportingMethod(reportMethod: unknown): 'GET' | 'POST' { if (typeof reportMethod === 'string') { switch (reportMethod.toUpperCase()) { case 'GET': @@ -272,8 +424,8 @@ function parseReportingMethod(reportMethod) { return 'GET'; } -function defineGlobalVariableName() { - function reportExternalWin(args) { +function defineGlobalVariableName(): void { + function reportExternalWin(args: any): boolean | void { return bidWon(args, true); } @@ -281,14 +433,14 @@ function defineGlobalVariableName() { globalName = `intentIqAnalyticsAdapter_${partnerId}`; identityGlobalName = `iiq_identity_${partnerId}`; - window[globalName] = { reportExternalWin }; + (window as any)[globalName as string] = { reportExternalWin }; } -function getRandom(start, end) { +function getRandom(start: number, end: number): number { return Math.floor(Math.random() * (end - start + 1) + start); } -export function preparePayload(data) { +export function preparePayload(data: any): Record | void { const result = getDefaultDataObject(); const fullUrl = getCurrentUrl(); result[PARAMS_NAMES.partnerId] = iiqAnalyticsAnalyticsAdapter.initOptions.partner; @@ -345,7 +497,7 @@ export function preparePayload(data) { return result; } -function fillEidsData(result) { +function fillEidsData(result: Record): void { if (iiqAnalyticsAnalyticsAdapter.initOptions.dataIdsInitialized) { result[PARAMS_NAMES.hadEidsInLocalStorage] = iiqAnalyticsAnalyticsAdapter.initOptions.eidl && iiqAnalyticsAnalyticsAdapter.initOptions.eidl > 0; @@ -353,7 +505,7 @@ function fillEidsData(result) { } } -function prepareData(data, result) { +function prepareData(data: any, result: Record): void { const adTypeValue = data.adType || data.mediaType; if (data.bidderCode) result.bidderCode = data.bidderCode; @@ -410,7 +562,7 @@ function prepareData(data, result) { if (data?.partnerAuctionId) result.partnerAuctionId = data.partnerAuctionId; } -function extractPlacementId(data) { +function extractPlacementId(data: any): string | null { if (data.placementId) { return data.placementId; } @@ -424,7 +576,7 @@ function extractPlacementId(data) { return null; } -function getDefaultDataObject() { +function getDefaultDataObject(): Record { return { inbbl: false, pbjsver: prebidVersion, @@ -438,17 +590,18 @@ function getDefaultDataObject() { }; } -function constructFullUrl(data) { - const report = []; +function constructFullUrl(data: Record): any { + const report: string[] = []; const reportMethod = iiqAnalyticsAnalyticsAdapter.initOptions.reportMethod; - const partnerData = window[identityGlobalName]?.partnerData; + const partnerData = (window as any)[identityGlobalName as string]?.partnerData; const currentBrowserLowerCase = detectBrowser(); const partnerAuctionId = data?.partnerAuctionId; - data = btoa(JSON.stringify(data)); - report.push(data); + const encodedData = btoa(JSON.stringify(data)); + report.push(encodedData); const cmpData = getCmpData(); - const baseUrl = reportingServerAddress(...getDataForDefineURL()); + const [reportEndpoint, region] = getDataForDefineURL(); + const baseUrl = reportingServerAddress(reportEndpoint, region); let url = baseUrl + @@ -471,12 +624,12 @@ function constructFullUrl(data) { PREBID + '&uh=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.clientHints) + - (isValidValue(cmpData.uspString) ? '&us_privacy=' + encodeURIComponent(cmpData.uspString) : '') + - (isValidValue(cmpData.gppString) ? '&gpp=' + encodeURIComponent(cmpData.gppString) : '') + + (isValidValue(cmpData.uspString) ? '&us_privacy=' + encodeURIComponent(cmpData.uspString as string) : '') + + (isValidValue(cmpData.gppString) ? '&gpp=' + encodeURIComponent(cmpData.gppString as string) : '') + (isValidValue(cmpData.gdprString) - ? '&gdpr_consent=' + encodeURIComponent(cmpData.gdprString) + '&gdpr=1' + ? '&gdpr_consent=' + encodeURIComponent(cmpData.gdprString as string) + '&gdpr=1' : '&gdpr=0') + - (cmpData.gdprApplies && isValidValue(cmpData.tcfApiVersion) ? '&tcfv=' + encodeURIComponent(cmpData.tcfApiVersion) : ''); + (cmpData.gdprApplies && isValidValue(cmpData.tcfApiVersion) ? '&tcfv=' + encodeURIComponent(cmpData.tcfApiVersion as string) : ''); url = appendSPData(url, partnerData); url = appendVrrefAndFui(url, iiqAnalyticsAnalyticsAdapter.initOptions.domainName); @@ -500,13 +653,13 @@ function constructFullUrl(data) { iiqAnalyticsAnalyticsAdapter.originEnableAnalytics = iiqAnalyticsAnalyticsAdapter.enableAnalytics; -iiqAnalyticsAnalyticsAdapter.enableAnalytics = function (myConfig) { +iiqAnalyticsAnalyticsAdapter.enableAnalytics = function (myConfig: any): void { iiqAnalyticsAnalyticsAdapter.originEnableAnalytics(myConfig); // call the base class function initAdapterConfig(myConfig); }; iiqAnalyticsAnalyticsAdapter.originDisableAnalytics = iiqAnalyticsAnalyticsAdapter.disableAnalytics; -iiqAnalyticsAnalyticsAdapter.disableAnalytics = function() { +iiqAnalyticsAnalyticsAdapter.disableAnalytics = function(): void { globalName = undefined; identityGlobalName = undefined; alreadySubscribedOnGAM = false; diff --git a/modules/intentIqIdSystem.d.ts b/modules/intentIqIdSystem.d.ts deleted file mode 100644 index 4b059f8bca2..00000000000 --- a/modules/intentIqIdSystem.d.ts +++ /dev/null @@ -1,135 +0,0 @@ -export type IntentIqIdSystemModuleName = 'intentIqId'; - -/** - * A/B testing configuration source — controls how the test group is assigned. - * - `'percentage'` — random assignment based on `abPercentage` - * - `'group'` — fixed group supplied via the `group` param - * - `'IIQServer'` — server-driven assignment (default) - * - `'disabled'` — A/B testing disabled; always use IIQ - */ -export type IntentIqABConfigSource = 'percentage' | 'group' | 'IIQServer' | 'disabled'; - -export interface IntentIqIdSystemParams { - /** - * Partner ID assigned by IntentIQ. Required. - */ - partner: number; - - /** - * Invoked when the identity lookup completes or times out. - * Receives the resolved EID payload or an empty string when the browser is - * blacklisted or the user is opted out. - */ - callback?: (data: { eids: unknown[] } | string) => void; - - /** - * Milliseconds to wait for the server response before firing `callback` - * with whatever data is currently available. Defaults to 500 ms. - */ - timeoutInMillis?: number; - - /** - * Comma-separated list of browser names (lowercase) that should be - * excluded from identity resolution, e.g. `'chrome,safari'`. - */ - browserBlackList?: string; - - /** - * Publisher domain name, used to build the referrer URL parameter. - */ - domainName?: string; - - /** - * When `true`, first-party data is stored under a partner-specific key so - * multiple IntentIQ configurations on the same page do not collide. - */ - siloEnabled?: boolean; - - /** - * Called whenever the resolved A/B group changes. - * Receives the new group (`'A'` | `'B'`) and the server termination-cause - * code when available. - */ - groupChanged?: (group: 'A' | 'B', terminationCause?: number) => void; - - /** - * Reference to the GAM `googletag.pubads()` object for automatic targeting - * key injection. - */ - gamObjectReference?: Record; - - /** - * GAM targeting key used to pass the A/B group. Defaults to - * `'intent_iq_group'`. - */ - gamParameterName?: string; - - /** - * Percentage of users placed in the WITH_IIQ (group A) cohort. - * Accepts 0–100; values outside that range are clamped. Defaults to 95. - * Only used when `ABTestingConfigurationSource` is `'percentage'` or - * `'IIQServer'` (no prior server termination cause). - */ - abPercentage?: number; - - /** - * Determines how the A/B test group is assigned. Defaults to `'IIQServer'`. - */ - ABTestingConfigurationSource?: IntentIqABConfigSource; - - /** - * Explicit A/B group override. Only used when - * `ABTestingConfigurationSource` is `'group'`. - */ - group?: 'A' | 'B'; - - /** - * Human-readable metadata tag describing the integration source - * (e.g. `'prebid'`, `'amp'`). Translated to a numeric code internally. - */ - sourceMetaData?: string; - - /** - * Numeric metadata code for the integration source when a specific - * override is required. - */ - sourceMetaDataExternal?: number; - - /** - * Freeform key-value pairs appended to every pixel request. - */ - additionalParams?: Record; - - /** - * Timeout in milliseconds for fetching Client Hints before falling back - * to an empty string. Defaults to 10 ms. - */ - chTimeout?: number; - - /** - * Partner-supplied first-party client identifier. - */ - partnerClientId?: string; - - /** - * Type code for `partnerClientId`. Must be a positive integer recognised - * by the IntentIQ server. - */ - partnerClientIdType?: number; -} - -declare module './userId/spec' { - interface UserId { - intentIqId: string; - } - - interface ProvidersToId { - intentIqId: 'intentIqId'; - } - - interface ProviderParams { - intentIqId: IntentIqIdSystemParams; - } -} - -export {}; diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.ts similarity index 72% rename from modules/intentIqIdSystem.js rename to modules/intentIqIdSystem.ts index 7fa11b72656..0a5d1b3cb7f 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.ts @@ -8,41 +8,178 @@ import { isNumber, isPlainObject, isStr, logError } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { detectBrowser } from '../libraries/intentIqUtils/detectBrowserUtils.js'; -import { appendSPData } from '../libraries/intentIqUtils/urlUtils.js'; -import { isCHSupported } from '../libraries/intentIqUtils/chUtils.js'; -import { appendVrrefAndFui } from '../libraries/intentIqUtils/getRefferer.js'; -import { getCmpData, areCmpValuesEqual, isValidValue } from '../libraries/intentIqUtils/getCmpData.js'; +import { detectBrowser } from '../libraries/intentIqUtils/detectBrowserUtils.ts'; +import { appendSPData } from '../libraries/intentIqUtils/urlUtils.ts'; +import { isCHSupported } from '../libraries/intentIqUtils/chUtils.ts'; +import { appendVrrefAndFui } from '../libraries/intentIqUtils/getRefferer.ts'; +import { getCmpData, areCmpValuesEqual, isValidValue } from '../libraries/intentIqUtils/getCmpData.ts'; import { + AllowedStorageType, defineStorageType, readData, removeDataByKey, storeData, tryParse -} from '../libraries/intentIqUtils/storageUtils.js'; +} from '../libraries/intentIqUtils/storageUtils.ts'; import { CLIENT_HINTS_KEY, FIRST_PARTY_KEY, GVLID, VERSION, INVALID_ID, SYNC_REFRESH_MILL, META_DATA_CONSTANT, PREBID, HOURS_72, CH_KEYS, DEFAULT_PERCENTAGE, WITH_IIQ -} from '../libraries/intentIqConstants/intentIqConstants.js'; -import { SYNC_KEY } from '../libraries/intentIqUtils/getSyncKey.js'; -import { getIiqServerAddress, iiqPixelServerAddress } from '../libraries/intentIqUtils/intentIqConfig.js'; -import { handleAdditionalParams } from '../libraries/intentIqUtils/handleAdditionalParams.js'; -import { decryptData, encryptData } from '../libraries/intentIqUtils/cryptionUtils.js'; -import { defineABTestingGroup } from '../libraries/intentIqUtils/defineABTestingGroupUtils.js'; +} from '../libraries/intentIqConstants/intentIqConstants.ts'; +import { SYNC_KEY } from '../libraries/intentIqUtils/getSyncKey.ts'; +import { getIiqServerAddress, iiqPixelServerAddress } from '../libraries/intentIqUtils/intentIqConfig.ts'; +import { handleAdditionalParams } from '../libraries/intentIqUtils/handleAdditionalParams.ts'; +import { decryptData, encryptData } from '../libraries/intentIqUtils/cryptionUtils.ts'; +import { defineABTestingGroup } from '../libraries/intentIqUtils/defineABTestingGroupUtils.ts'; import { setKeyValueOn } from '../libraries/gptUtils/gptUtils.js'; +import type { IdProviderSpec } from './userId/spec'; + + +export type IntentIqIdSystemModuleName = 'intentIqId'; + /** - * @typedef {import('../modules/userId/index.js').Submodule} Submodule - * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig - * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + * A/B testing configuration source — controls how the test group is assigned. + * - `'percentage'` — random assignment based on `abPercentage` + * - `'group'` — fixed group supplied via the `group` param + * - `'IIQServer'` — server-driven assignment (default) + * - `'disabled'` — A/B testing disabled; always use IIQ */ +export type IntentIqABConfigSource = 'percentage' | 'group' | 'IIQServer' | 'disabled'; + +export interface IntentIqIdSystemParams { + /** + * Partner ID assigned by IntentIQ. Required. + */ + partner: number; + + /** + * Invoked when the identity lookup completes or times out. + * Receives the resolved EID payload or an empty string when the browser is + * blacklisted or the user is opted out. + */ + callback?: (data: { eids: unknown[] } | string) => void; + + /** + * Milliseconds to wait for the server response before firing `callback` + * with whatever data is currently available. Defaults to 500 ms. + */ + timeoutInMillis?: number; + + /** + * Comma-separated list of browser names (lowercase) that should be + * excluded from identity resolution, e.g. `'chrome,safari'`. + */ + browserBlackList?: string; + + /** + * Publisher domain name, used to build the referrer URL parameter. + */ + domainName?: string; + + /** + * When `true`, first-party data is stored under a partner-specific key so + * multiple IntentIQ configurations on the same page do not collide. + */ + siloEnabled?: boolean; + + /** + * Called whenever the resolved A/B group changes. + * Receives the new group (`'A'` | `'B'`) and the server termination-cause + * code when available. + */ + groupChanged?: (group: 'A' | 'B', terminationCause?: number) => void; + + /** + * Reference to the GAM `googletag.pubads()` object for automatic targeting + * key injection. + */ + gamObjectReference?: Record; + + /** + * GAM targeting key used to pass the A/B group. Defaults to + * `'intent_iq_group'`. + */ + gamParameterName?: string; + + /** + * Percentage of users placed in the WITH_IIQ (group A) cohort. + * Accepts 0–100; values outside that range are clamped. Defaults to 95. + * Only used when `ABTestingConfigurationSource` is `'percentage'` or + * `'IIQServer'` (no prior server termination cause). + */ + abPercentage?: number; + + /** + * Determines how the A/B test group is assigned. Defaults to `'IIQServer'`. + */ + ABTestingConfigurationSource?: IntentIqABConfigSource; + + /** + * Explicit A/B group override. Only used when + * `ABTestingConfigurationSource` is `'group'`. + */ + group?: 'A' | 'B'; + + /** + * Human-readable metadata tag describing the integration source + * (e.g. `'prebid'`, `'amp'`). Translated to a numeric code internally. + */ + sourceMetaData?: string; + + /** + * Numeric metadata code for the integration source when a specific + * override is required. + */ + sourceMetaDataExternal?: number; + + /** + * Freeform key-value pairs appended to every pixel request. + */ + additionalParams?: Record; + + /** + * Timeout in milliseconds for fetching Client Hints before falling back + * to an empty string. Defaults to 10 ms. + */ + chTimeout?: number; + + /** + * Partner-supplied first-party client identifier. + */ + partnerClientId?: string; + + /** + * Type code for `partnerClientId`. Must be a positive integer recognised + * by the IntentIQ server. + */ + partnerClientIdType?: number; + + /** + * Partner-supplied Advertiser ID + */ + pai?: string; +} + +declare module './userId/spec' { + interface UserId { + intentIqId: string; + } + + interface ProvidersToId { + intentIqId: 'intentIqId'; + } + + interface ProviderParams { + intentIqId: IntentIqIdSystemParams; + } +} -const MODULE_NAME = 'intentIqId'; +const MODULE_NAME = 'intentIqId' as const; -const encoderCH = { +const encoderCH: Record = { brands: 0, mobile: 1, platform: 2, @@ -53,22 +190,22 @@ const encoderCH = { wow64: 7, fullVersionList: 8 }; -let sourceMetaData; -let sourceMetaDataExternal; +let sourceMetaData: number | string | undefined; +let sourceMetaDataExternal: number | undefined; let globalName = ''; let FIRST_PARTY_KEY_FINAL = FIRST_PARTY_KEY; -let PARTNER_DATA_KEY; +let PARTNER_DATA_KEY: string = ''; let callCount = 0; let failCount = 0; let noDataCount = 0; -export let firstPartyData; -let partnerData; -let clientHints; -let actualABGroup; +export let firstPartyData: any; +let partnerData: any; +let clientHints: string | null | undefined; +let actualABGroup: IntentIqIdSystemParams['group'] | undefined; -function getEffectiveAbPercentage(abPercentage) { +function getEffectiveAbPercentage(abPercentage: unknown): number { const n = Number(abPercentage); if (!Number.isFinite(n)) return DEFAULT_PERCENTAGE; return Math.max(0, Math.min(100, n)); @@ -78,9 +215,9 @@ function getEffectiveAbPercentage(abPercentage) { * Generate standard UUID string * @return {string} */ -function generateGUID() { +function generateGUID(): string { let d = new Date().getTime(); - const guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c: string) { const r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); @@ -88,24 +225,24 @@ function generateGUID() { return guid; } -function addUniquenessToUrl(url) { +function addUniquenessToUrl(url: string): string { url += '&tsrnd=' + Math.floor(Math.random() * 1000) + '_' + new Date().getTime(); return url; } -function appendFirstPartyData(url, firstPartyData, partnerData) { +function appendFirstPartyData(url: string, firstPartyData: any, partnerData: any): string { url += firstPartyData.pid ? '&pid=' + encodeURIComponent(firstPartyData.pid) : ''; url += firstPartyData.pcid ? '&iiqidtype=2&iiqpcid=' + encodeURIComponent(firstPartyData.pcid) : ''; url += firstPartyData.pcidDate ? '&iiqpciddate=' + encodeURIComponent(firstPartyData.pcidDate) : ''; return url; } -function verifyIdType(value) { +function verifyIdType(value: number): number { if (value === 0 || value === 1 || value === 3 || value === 4) return value; return -1; } -function appendPartnersFirstParty(url, configParams) { +function appendPartnersFirstParty(url: string, configParams: any): string { const partnerClientId = typeof configParams.partnerClientId === 'string' ? encodeURIComponent(configParams.partnerClientId) : ''; const partnerClientIdType = typeof configParams.partnerClientIdType === 'number' ? verifyIdType(configParams.partnerClientIdType) : -1; @@ -117,7 +254,7 @@ function appendPartnersFirstParty(url, configParams) { return url; } -function appendCMPData(url, cmpData) { +function appendCMPData(url: string, cmpData: any): string { url += isValidValue(cmpData.uspString) ? '&us_privacy=' + encodeURIComponent(cmpData.uspString) : ''; url += isValidValue(cmpData.gppString) ? '&gpp=' + encodeURIComponent(cmpData.gppString) : ''; if (cmpData.gdprApplies) { @@ -130,7 +267,7 @@ function appendCMPData(url, cmpData) { return url; } -function appendCounters(url) { +function appendCounters(url: string): string { url += '&jaesc=' + encodeURIComponent(callCount); url += '&jafc=' + encodeURIComponent(failCount); url += '&jaensc=' + encodeURIComponent(noDataCount); @@ -140,7 +277,7 @@ function appendCounters(url) { /** * Translate and validate sourceMetaData */ -export function translateMetadata(data) { +export function translateMetadata(data: string): number { try { const d = data.split('.'); return ( @@ -155,23 +292,23 @@ export function translateMetadata(data) { /** * Add sourceMetaData to URL if valid */ -function addMetaData(url, data) { +function addMetaData(url: string, data: unknown): string { if (typeof data !== 'number' || isNaN(data)) { return url; } return url + '&fbp=' + data; } -export function initializeGlobalIIQ(partnerId) { - if (!globalName || !window[globalName]) { +export function initializeGlobalIIQ(partnerId: number): boolean { + if (!globalName || !(window as any)[globalName]) { globalName = `iiq_identity_${partnerId}`; - window[globalName] = {}; + (window as any)[globalName] = {}; return true; } return false; } -export function createPixelUrl(firstPartyData, clientHints, configParams, partnerData, cmpData) { +export function createPixelUrl(firstPartyData: any, clientHints: string, configParams: any, partnerData: any, cmpData: any): string { const browser = detectBrowser(); let url = iiqPixelServerAddress(configParams); @@ -197,8 +334,8 @@ export function createPixelUrl(firstPartyData, clientHints, configParams, partne return url; } -function sendSyncRequest(allowedStorage, url, partner, firstPartyData, newUser) { - const lastSyncDate = Number(readData(SYNC_KEY(partner) || '', allowedStorage)) || false; +function sendSyncRequest(allowedStorage: any, url: string, partner: number, firstPartyData: any, newUser: boolean): void { + const lastSyncDate: any = Number(readData(SYNC_KEY(partner) || '', allowedStorage)) || false; const lastSyncElapsedTime = Date.now() - lastSyncDate; if (firstPartyData.isOptedOut) { @@ -225,7 +362,7 @@ 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, isBlacklisted = false) { +export function setGamReporting(gamObjectReference: any, gamParameterName: string, userGroup: any, isBlacklisted = false): void { if (isBlacklisted) return; if (isPlainObject(gamObjectReference) && gamObjectReference.cmd) { setKeyValueOn(gamParameterName, userGroup, gamObjectReference); @@ -237,13 +374,13 @@ export function setGamReporting(gamObjectReference, gamParameterName, userGroup, * @param {object} clientHints - Raw client hints data * @return {string} A JSON string of processed client hints or an empty string if no hints */ -export function handleClientHints(clientHints) { - const chParams = {}; +export function handleClientHints(clientHints: any): string { + const chParams: Record = {}; for (const key in clientHints) { if (clientHints.hasOwnProperty(key) && clientHints[key] !== '') { if (['brands', 'fullVersionList'].includes(key)) { let handledParam = ''; - clientHints[key].forEach((element, index) => { + clientHints[key].forEach((element: any, index: number) => { const isNotLast = index < clientHints[key].length - 1; handledParam += `"${element.brand}";v="${element.version}"${isNotLast ? ', ' : ''}`; }); @@ -258,13 +395,13 @@ export function handleClientHints(clientHints) { return Object.keys(chParams).length ? JSON.stringify(chParams) : ''; } -export function isCMPStringTheSame(fpData, cmpData) { - return ['gdprString', 'gppString', 'uspString'].every(field => +export function isCMPStringTheSame(fpData: any, cmpData: any): boolean { + return ['gdprString', 'gppString', 'uspString'].every((field: string) => areCmpValuesEqual(fpData[field], cmpData[field]) ); } -function updateCountersAndStore(runtimeEids, allowedStorage, partnerData) { +function updateCountersAndStore(runtimeEids: any, allowedStorage: any, partnerData: any): void { if (!runtimeEids?.eids?.length) { noDataCount++; } else { @@ -273,21 +410,20 @@ function updateCountersAndStore(runtimeEids, allowedStorage, partnerData) { storeCounters(allowedStorage, partnerData); } -function clearCountersAndStore(allowedStorage, partnerData) { +function clearCountersAndStore(allowedStorage: any, partnerData: any): void { callCount = 0; failCount = 0; noDataCount = 0; storeCounters(allowedStorage, partnerData); } -function storeCounters(allowedStorage, partnerData) { +function storeCounters(allowedStorage: any, partnerData: any): void { partnerData.callCount = callCount; partnerData.failCount = failCount; partnerData.noDataCounter = noDataCount; storeData(PARTNER_DATA_KEY, JSON.stringify(partnerData), allowedStorage, firstPartyData); } -/** @type {Submodule} */ export const intentIqIdSubmodule = { /** * used to link submodule with config @@ -301,7 +437,7 @@ export const intentIqIdSubmodule = { * @param {{string}} value * @returns {{intentIqId: {string}}|undefined} */ - decode(value) { + decode(value: string) { return value && INVALID_ID !== value ? { 'intentIqId': value } : undefined; }, @@ -312,9 +448,9 @@ export const intentIqIdSubmodule = { * @returns {IdResponse|undefined} */ getId(config) { - const configParams = (config?.params) || {}; + const configParams: IntentIqIdSystemParams = (config.params ?? {}) as unknown as IntentIqIdSystemParams; - const firePartnerCallback = () => { + const firePartnerCallback = (): void => { if (configParams.callback && !callbackFired) { callbackFired = true; if (callbackTimeoutID) clearTimeout(callbackTimeoutID); @@ -333,29 +469,29 @@ export const intentIqIdSubmodule = { initializeGlobalIIQ(configParams.partner); - let decryptedData, callbackTimeoutID; + let decryptedData: any, callbackTimeoutID: ReturnType | undefined; let callbackFired = false; - let runtimeEids = { eids: [] }; + let runtimeEids: any = { eids: [] }; const gamObjectReference = isPlainObject(configParams.gamObjectReference) ? configParams.gamObjectReference : undefined; const gamParameterName = configParams.gamParameterName ? configParams.gamParameterName : 'intent_iq_group'; const groupChanged = typeof configParams.groupChanged === 'function' ? configParams.groupChanged : undefined; const siloEnabled = typeof configParams.siloEnabled === 'boolean' ? configParams.siloEnabled : false; - sourceMetaData = isStr(configParams.sourceMetaData) ? translateMetadata(configParams.sourceMetaData) : ''; + sourceMetaData = isStr(configParams.sourceMetaData) ? translateMetadata(configParams.sourceMetaData as string) : ''; sourceMetaDataExternal = isNumber(configParams.sourceMetaDataExternal) ? configParams.sourceMetaDataExternal : undefined; const additionalParams = configParams.additionalParams ? configParams.additionalParams : undefined; const chTimeout = Number(configParams?.chTimeout) >= 0 ? Number(configParams.chTimeout) : 10; PARTNER_DATA_KEY = `${FIRST_PARTY_KEY}_${configParams.partner}`; - const allowedStorage = defineStorageType(config.enabledStorageTypes); - partnerData = tryParse(readData(PARTNER_DATA_KEY, allowedStorage)) || {}; + const allowedStorage: AllowedStorageType[] = defineStorageType((config as any).enabledStorageTypes); + partnerData = tryParse(readData(PARTNER_DATA_KEY, allowedStorage) as string) || {}; let rrttStrtTime = 0; let shouldCallServer = false; FIRST_PARTY_KEY_FINAL = `${FIRST_PARTY_KEY}${siloEnabled ? '_p_' + configParams.partner : ''}`; const cmpData = getCmpData(); const gdprDetected = cmpData.gdprString; - firstPartyData = tryParse(readData(FIRST_PARTY_KEY_FINAL, allowedStorage)); + firstPartyData = tryParse(readData(FIRST_PARTY_KEY_FINAL, allowedStorage) as string); actualABGroup = defineABTestingGroup(configParams, partnerData?.terminationCause); if (groupChanged) groupChanged(actualABGroup, partnerData?.terminationCause); const currentBrowserLowerCase = detectBrowser(); @@ -395,11 +531,11 @@ export const intentIqIdSubmodule = { // Read client hints from storage clientHints = readData(CLIENT_HINTS_KEY, allowedStorage); const chSupported = isCHSupported(); - let chPromise = null; + let chPromise: Promise | null = null; - function fetchAndHandleCH() { - return navigator.userAgentData.getHighEntropyValues(CH_KEYS) - .then(raw => { + function fetchAndHandleCH(): Promise { + return (navigator as any).userAgentData.getHighEntropyValues(CH_KEYS) + .then((raw: any) => { const nextCH = handleClientHints(raw) || ''; const prevCH = clientHints || ''; if (nextCH !== prevCH) { @@ -408,7 +544,7 @@ export const intentIqIdSubmodule = { } return nextCH; }) - .catch(err => { + .catch((err: any) => { logError('CH fetch failed', err); if (clientHints !== '') { clientHints = ''; @@ -420,7 +556,7 @@ export const intentIqIdSubmodule = { if (chSupported) { chPromise = fetchAndHandleCH(); - chPromise.catch(err => { + chPromise.catch((err: any) => { logError('fetchAndHandleCH failed', err); }); } else { @@ -428,8 +564,8 @@ export const intentIqIdSubmodule = { removeDataByKey(CLIENT_HINTS_KEY, allowedStorage); } - function waitOnCH(timeoutMs) { - const timeout = new Promise(resolve => setTimeout(() => resolve(''), timeoutMs)); + function waitOnCH(timeoutMs: number): Promise { + const timeout = new Promise(resolve => setTimeout(() => resolve(''), timeoutMs)); return Promise.race([chPromise, timeout]); } @@ -448,14 +584,14 @@ export const intentIqIdSubmodule = { } } - function updateGlobalObj() { + function updateGlobalObj(): void { if (globalName) { - window[globalName].partnerData = partnerData; - window[globalName].firstPartyData = firstPartyData; - window[globalName].clientHints = clientHints; - window[globalName].actualABGroup = actualABGroup; - window[globalName].abPercentage = getEffectiveAbPercentage(configParams.abPercentage); - window[globalName].userProvidedAbPercentage = configParams.abPercentage; + (window as any)[globalName].partnerData = partnerData; + (window as any)[globalName].firstPartyData = firstPartyData; + (window as any)[globalName].clientHints = clientHints; + (window as any)[globalName].actualABGroup = actualABGroup; + (window as any)[globalName].abPercentage = getEffectiveAbPercentage(configParams.abPercentage); + (window as any)[globalName].userProvidedAbPercentage = configParams.abPercentage; } } @@ -487,7 +623,7 @@ export const intentIqIdSubmodule = { firePartnerCallback(); } - function buildAndSendPixel(ch) { + function buildAndSendPixel(ch: string): void { const url = createPixelUrl(firstPartyData, ch, configParams, partnerData, cmpData); sendSyncRequest(allowedStorage, url, configParams.partner, firstPartyData, newUser); } @@ -502,7 +638,7 @@ export const intentIqIdSubmodule = { buildAndSendPixel(clientHints); } else { waitOnCH(chTimeout) - .then(ch => buildAndSendPixel(ch || '')); + .then((ch: any) => buildAndSendPixel(ch || '')); } } else { buildAndSendPixel(''); @@ -519,7 +655,7 @@ export const intentIqIdSubmodule = { updateGlobalObj(); // update global object before server request, to make sure analytical adapter will have it even if the server is "not in time" // use protocol relative urls for http or https - let url = `${getIiqServerAddress(configParams)}/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`; + let url = `${getIiqServerAddress(configParams as any)}/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`; url += configParams.pai ? '&pai=' + encodeURIComponent(configParams.pai) : ''; url = appendFirstPartyData(url, firstPartyData, partnerData); url = appendPartnersFirstParty(url, configParams); @@ -543,24 +679,24 @@ export const intentIqIdSubmodule = { // Add vrref and fui to the URL url = appendVrrefAndFui(url, configParams.domainName); - const storeFirstPartyData = () => { + const storeFirstPartyData = (): void => { partnerData.eidl = runtimeEids?.eids?.length || -1; storeData(FIRST_PARTY_KEY_FINAL, JSON.stringify(firstPartyData), allowedStorage, firstPartyData); storeData(PARTNER_DATA_KEY, JSON.stringify(partnerData), allowedStorage, firstPartyData); }; - const resp = function (callback) { + const resp = function (callback: (value: any) => void) { const callbacks = { - success: response => { + success: (response: any) => { if (rrttStrtTime && rrttStrtTime > 0) { partnerData.rrtt = Date.now() - rrttStrtTime; } - const respJson = tryParse(response); + const respJson = tryParse(response) as any; // If response is a valid json and should save is true if (respJson) { partnerData.date = Date.now(); firstPartyData.sCal = Date.now(); - const defineEmptyDataAndFireCallback = () => { + const defineEmptyDataAndFireCallback = (): void => { respJson.data = partnerData.data = runtimeEids = { eids: [] }; storeFirstPartyData(); firePartnerCallback(); @@ -662,7 +798,7 @@ export const intentIqIdSubmodule = { firePartnerCallback(); } }, - error: error => { + error: (error: any) => { logError(MODULE_NAME + ': ID fetch encountered an error', error); failCount++; updateCountersAndStore(runtimeEids, allowedStorage, partnerData); @@ -676,7 +812,7 @@ export const intentIqIdSubmodule = { rrttStrtTime = Date.now(); - const sendAjax = uh => { + const sendAjax = (uh: string) => { if (uh) url += '&uh=' + encodeURIComponent(uh); ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true }); }; @@ -687,7 +823,7 @@ export const intentIqIdSubmodule = { sendAjax(clientHints); } else { // No CH in LS: wait up to chTimeout, then send - waitOnCH(chTimeout).then(ch => { + waitOnCH(chTimeout).then((ch: any) => { // Send with received CH or without it sendAjax(ch || ''); }); @@ -697,25 +833,25 @@ export const intentIqIdSubmodule = { sendAjax(''); } }; - const respObj = { callback: resp }; + const respObj: any = { callback: resp }; if (runtimeEids?.eids?.length) respObj.id = runtimeEids.eids; return respObj; }, eids: { - 'intentIqId': { + [MODULE_NAME]: { source: 'intentiq.com', - atype: 1, - getSource: function (data) { + atype: '1', + getSource: function (data: any) { return data.source; }, - getValue: function (data) { + getValue: function (data: any) { if (data?.uids?.length) { return data.uids[0].id; } return null; }, - getUidExt: function (data) { + getUidExt: function (data: any) { if (data?.uids?.length) { return data.uids[0].ext; }