diff --git a/libraries/intentIqConstants/intentIqConstants.js b/libraries/intentIqConstants/intentIqConstants.js new file mode 100644 index 00000000000..46c466e936f --- /dev/null +++ b/libraries/intentIqConstants/intentIqConstants.js @@ -0,0 +1,10 @@ +export const FIRST_PARTY_KEY = '_iiq_fdata'; + +export const WITH_IIQ = 'A'; +export const WITHOUT_IIQ = 'B'; +export const NOT_YET_DEFINED = 'U'; +export const OPT_OUT = 'O'; +export const BLACK_LIST = 'L'; +export const CLIENT_HINTS_KEY = '_iiq_ch'; +export const EMPTY = 'EMPTY' +export const VERSION = 0.21 diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js index 76615730dd5..7962080a138 100644 --- a/modules/intentIqAnalyticsAdapter.js +++ b/modules/intentIqAnalyticsAdapter.js @@ -1,24 +1,21 @@ -import { logInfo, logError, getWindowSelf, getWindowTop, getWindowLocation } from '../src/utils.js'; +import {getWindowLocation, getWindowSelf, getWindowTop, logError, logInfo} from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import { ajax } from '../src/ajax.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { config } from '../src/config.js'; -import { EVENTS } from '../src/constants.js'; -import { MODULE_TYPE_ANALYTICS } from '../src/activities/modules.js'; -import { detectBrowser } from '../libraries/detectBrowserUtils/detectBrowserUtils.js'; +import {ajax} from '../src/ajax.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {config} from '../src/config.js'; +import {EVENTS} from '../src/constants.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; +import {detectBrowser} from '../libraries/detectBrowserUtils/detectBrowserUtils.js'; +import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY, VERSION} from '../libraries/intentIqConstants/intentIqConstants.js'; const MODULE_NAME = 'iiqAnalytics' const analyticsType = 'endpoint'; const defaultUrl = 'https://reports.intentiq.com/report'; -const storage = getStorageManager({ moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_NAME }); +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_NAME}); const prebidVersion = '$prebid.version$'; export const REPORTER_ID = Date.now() + '_' + getRandom(0, 1000); -const FIRST_PARTY_KEY = '_iiq_fdata'; -const FIRST_PARTY_DATA_KEY = '_iiq_fdata'; -const JSVERSION = 0.2 - const PARAMS_NAMES = { abTestGroup: 'abGroup', pbPauseUntil: 'pbPauseUntil', @@ -55,7 +52,7 @@ const PARAMS_NAMES = { firstPartyId: 'pcid' }; -let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ defaultUrl, analyticsType }), { +let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({defaultUrl, analyticsType}), { initOptions: { lsValueInitialized: false, partner: null, @@ -64,13 +61,16 @@ let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ defaultUrl, analytics dataInLs: null, eidl: null, lsIdsInitialized: false, - manualReport: false + manualWinReportEnabled: false }, - track({ eventType, args }) { + track({eventType, args}) { switch (eventType) { case BID_WON: bidWon(args); break; + case BID_REQUESTED: + defineGlobalVariableName(); + break; default: break; } @@ -79,7 +79,8 @@ let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ defaultUrl, analytics // Events needed const { - BID_WON + BID_WON, + BID_REQUESTED } = EVENTS; function readData(key) { @@ -113,6 +114,7 @@ function initLsValues() { iiqAnalyticsAnalyticsAdapter.initOptions.partner = iiqArr[0].params.partner; } iiqAnalyticsAnalyticsAdapter.initOptions.browserBlackList = typeof iiqArr[0].params.browserBlackList === 'string' ? iiqArr[0].params.browserBlackList.toLowerCase() : ''; + iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = iiqArr[0].params.manualWinReportEnabled || false } } @@ -123,20 +125,27 @@ function initReadLsIds() { if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid) { iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup = iiqAnalyticsAnalyticsAdapter.initOptions.fpid.group; } - let iData = readData(FIRST_PARTY_DATA_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner); - if (iData) { + const partnerData = readData(FIRST_PARTY_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner); + const clientsHints = readData(CLIENT_HINTS_KEY) || ''; + + if (partnerData) { iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized = true; - let pData = JSON.parse(iData); + let pData = JSON.parse(partnerData); + iiqAnalyticsAnalyticsAdapter.initOptions.terminationCause = pData.terminationCause iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs = pData.data; iiqAnalyticsAnalyticsAdapter.initOptions.eidl = pData.eidl || -1; } + + iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints = clientsHints } catch (e) { logError(e) } } -function bidWon(args) { - if (!iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized) { initLsValues(); } +function bidWon(args, isReportExternal) { + if (!iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized) { + initLsValues(); + } if (isNaN(iiqAnalyticsAnalyticsAdapter.initOptions.partner) || iiqAnalyticsAnalyticsAdapter.initOptions.partner == -1) return; @@ -146,12 +155,31 @@ function bidWon(args) { return; } - if (iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized && !iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized) { initReadLsIds(); } - if (!iiqAnalyticsAnalyticsAdapter.initOptions.manualReport) { - ajax(constructFullUrl(preparePayload(args, true)), undefined, null, { method: 'GET' }); + if (iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized && !iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized) { + initReadLsIds(); + } + if ((isReportExternal && iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled) || (!isReportExternal && !iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled)) { + ajax(constructFullUrl(preparePayload(args, true)), undefined, null, {method: 'GET'}); + logInfo('IIQ ANALYTICS -> BID WON') + return true; + } + return false; +} + +function defineGlobalVariableName() { + function reportExternalWin(args) { + return bidWon(args, true) } - logInfo('IIQ ANALYTICS -> BID WON') + let partnerId = 0 + const userConfig = config.getConfig('userSync.userIds') + + if (userConfig) { + const iiqArr = userConfig.filter(m => m.name == 'intentIqId'); + if (iiqArr.length) partnerId = iiqArr[0].params.partner + } + + window[`intentIqAnalyticsAdapter_${partnerId}`] = {reportExternalWin: reportExternalWin} } function getRandom(start, end) { @@ -160,11 +188,11 @@ function getRandom(start, end) { export function preparePayload(data) { let result = getDefaultDataObject(); - + readData(FIRST_PARTY_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner); result[PARAMS_NAMES.partnerId] = iiqAnalyticsAnalyticsAdapter.initOptions.partner; result[PARAMS_NAMES.prebidVersion] = prebidVersion; result[PARAMS_NAMES.referrer] = getReferrer(); - + result[PARAMS_NAMES.terminationCause] = iiqAnalyticsAnalyticsAdapter.initOptions.terminationCause; result[PARAMS_NAMES.abTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup; result[PARAMS_NAMES.isInTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup == 'A'; @@ -187,13 +215,27 @@ function fillEidsData(result) { } function fillPrebidEventData(eventData, result) { - if (eventData.bidderCode) { result.bidderCode = eventData.bidderCode; } - if (eventData.cpm) { result.cpm = eventData.cpm; } - if (eventData.currency) { result.currency = eventData.currency; } - if (eventData.originalCpm) { result.originalCpm = eventData.originalCpm; } - if (eventData.originalCurrency) { result.originalCurrency = eventData.originalCurrency; } - if (eventData.status) { result.status = eventData.status; } - if (eventData.auctionId) { result.prebidAuctionId = eventData.auctionId; } + if (eventData.bidderCode) { + result.bidderCode = eventData.bidderCode; + } + if (eventData.cpm) { + result.cpm = eventData.cpm; + } + if (eventData.currency) { + result.currency = eventData.currency; + } + if (eventData.originalCpm) { + result.originalCpm = eventData.originalCpm; + } + if (eventData.originalCurrency) { + result.originalCurrency = eventData.originalCurrency; + } + if (eventData.status) { + result.status = eventData.status; + } + if (eventData.auctionId) { + result.prebidAuctionId = eventData.auctionId; + } result.biddingPlatformId = 1; result.partnerAuctionId = 'BW'; @@ -206,7 +248,7 @@ function getDefaultDataObject() { 'partnerAuctionId': 'BW', 'reportSource': 'pbjs', 'abGroup': 'U', - 'jsversion': JSVERSION, + 'jsversion': VERSION, 'partnerId': -1, 'biddingPlatformId': 1, 'idls': false, @@ -224,18 +266,19 @@ function constructFullUrl(data) { ((iiqAnalyticsAnalyticsAdapter.initOptions && iiqAnalyticsAnalyticsAdapter.initOptions.fpid) ? '&iiqid=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid) : '') + '&agid=' + REPORTER_ID + - '&jsver=' + JSVERSION + + '&jsver=' + VERSION + '&vrref=' + getReferrer() + '&source=pbjs' + - '&payload=' + JSON.stringify(report) + '&payload=' + JSON.stringify(report) + + '&uh=' + iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints } export function getReferrer() { try { if (getWindowSelf() === getWindowTop()) { - return getWindowLocation().href; + return encodeURIComponent(getWindowLocation().href); } else { - return getWindowTop().location.href; + return encodeURIComponent(getWindowTop().location.href); } } catch (error) { logError(`Error accessing location: ${error}`); diff --git a/modules/intentIqAnalyticsAdapter.md b/modules/intentIqAnalyticsAdapter.md index 905d88a9b21..2a3eece0576 100644 --- a/modules/intentIqAnalyticsAdapter.md +++ b/modules/intentIqAnalyticsAdapter.md @@ -16,8 +16,6 @@ No registration for this module is required. IMPORTANT: only effective when Intent IQ Universal ID module is installed and configured. [(How-To)](https://docs.prebid.org/dev-docs/modules/userid-submodules/intentiq.html) -No additional configuration for this module is required. We will use the configuration provided for Intent IQ Universal IQ module. - #### Example Configuration ```js @@ -25,3 +23,63 @@ pbjs.enableAnalytics({ provider: 'iiqAnalytics' }); ``` + + +### Manual Report Trigger with reportExternalWin + +The reportExternalWin function allows for manual reporting, meaning that reports will not be sent automatically but only when triggered manually. + +To enable this manual reporting functionality, you must set the manualWinReportEnabled parameter in Intent IQ Unified ID module configuration is true. Once enabled, reports can be manually triggered using the reportExternalWin function. + + +### Calling the reportExternalWin Function + +To call the reportExternalWin function, you need to pass the partner_id parameter as shown in the example below: + +```js +window.intentIqAnalyticsAdapter_[partner_id].reportExternalWin() +``` +Example use with Partner ID = 123455 + +```js +window.intentIqAnalyticsAdapter_123455.reportExternalWin() +``` + +### Function Parameters + +The reportExternalWin function takes an object containing auction win data. Below is an example of the object: + +```js +var reportData = { +biddingPlatformId: 1, // Platform ID. The value 1 corresponds to PreBid. +partnerAuctionId: '[YOUR_AUCTION_ID_IF_EXISTS]', // Auction ID, if available. +bidderCode: 'xxxxxxxx', // Bidder code. +prebidAuctionId: '3d4xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx8e', // PreBid auction ID. +cpm: 1.5, // Cost per thousand impressions (CPM). +currency: 'USD', // Currency for the CPM value. +originalCpm: 1.5, // Original CPM value. +originalCurrency: 'USD', // Original currency. +status: 'rendered', // Auction status, e.g., 'rendered'. +placementId: 'div-1' // ID of the ad placement. +} +``` + +| Field | Data Type | Description | Example | Mandatory | +|--------------------|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------|-----------| +| biddingPlatformId | Integer | Specify the platform in which this ad impression was rendered – 1 – Prebid, 2 – Amazon, 3 – Google, 4 – Open RTB (including your local Prebid server) | 1 | Yes | +| partnerAuctionId | String | Use this when you are running multiple auction solutions across your assets and have a unified identifier for auctions | 3d44542d-xx-4662-xxxx-4xxxx3d8e | No | +| bidderCode | String | Specifies the name of the bidder that won the auction as reported by Prebid and all other bidding platforms | newAppnexus | Yes | +| prebidAuctionId | String | Specifies the identifier of the Prebid auction. Leave empty or undefined if Prebid is not the bidding platform | | | +| cpm | Decimal | Cost per mille of the impression as received from the demand-side auction (without modifications or reductions) | 5.62 | Yes | +| currency | String | Currency of the auction | USD | Yes | +| originalCpm | Decimal | Leave empty or undefined if Prebid is not the bidding platform | 5.5 | No | +| originalCurrency | String | Currency of the original auction | USD | No | +| status | String | Status of the impression. Leave empty or undefined if Prebid is not the bidding platform | rendered | No | +| placementId | String | Unique identifier of the ad unit on the webpage that showed this ad | div-1 | No | + + +To report the auction win, call the function as follows: + +```js +window.intentIqAnalyticsAdapter_[partner_id].reportExternalWin(reportData) +``` diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 2702aa9848d..069a955477b 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -5,15 +5,25 @@ * @requires module:modules/userId */ -import { logError, logInfo } from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; -import { submodule } from '../src/hook.js' -import { getStorageManager } from '../src/storageManager.js'; -import { MODULE_TYPE_UID } from '../src/activities/modules.js'; -import { gppDataHandler, uspDataHandler } from '../src/consentHandler.js'; +import {logError, logInfo} from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js' +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import {gppDataHandler, uspDataHandler} from '../src/consentHandler.js'; import AES from 'crypto-js/aes.js'; import Utf8 from 'crypto-js/enc-utf8.js'; -import { detectBrowser } from '../libraries/detectBrowserUtils/detectBrowserUtils.js'; +import {detectBrowser} from '../libraries/detectBrowserUtils/detectBrowserUtils.js'; +import { + FIRST_PARTY_KEY, + WITH_IIQ, WITHOUT_IIQ, + NOT_YET_DEFINED, + OPT_OUT, + BLACK_LIST, + CLIENT_HINTS_KEY, + EMPTY, + VERSION +} from '../libraries/intentIqConstants/intentIqConstants.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -24,16 +34,6 @@ import { detectBrowser } from '../libraries/detectBrowserUtils/detectBrowserUtil const PCID_EXPIRY = 365; const MODULE_NAME = 'intentIqId'; -export const FIRST_PARTY_KEY = '_iiq_fdata'; -export let FIRST_PARTY_DATA_KEY = '_iiq_fdata'; -export const WITH_IIQ = 'A'; -export const WITHOUT_IIQ = 'B'; -export const NOT_YET_DEFINED = 'U'; -export const OPT_OUT = 'O'; -export const BLACK_LIST = 'L'; -export const CLIENT_HINTS_KEY = '_iiq_ch'; -export const EMPTY = 'EMPTY' -export const VERSION = 0.2 const encoderCH = { brands: 0, @@ -49,7 +49,7 @@ const encoderCH = { const INVALID_ID = 'INVALID_ID'; const SUPPORTED_TYPES = ['html5', 'cookie'] -export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** * Generate standard UUID string @@ -219,7 +219,7 @@ export const intentIqIdSubmodule = { * @returns {{intentIqId: {string}}|undefined} */ decode(value) { - return value && value != '' && INVALID_ID != value ? { 'intentIqId': value } : undefined; + return value && value != '' && INVALID_ID != value ? {'intentIqId': value} : undefined; }, /** * performs action to obtain id and return a value in the callback's response argument @@ -233,6 +233,10 @@ export const intentIqIdSubmodule = { let callbackFired = false let runtimeEids = {} + const allowedStorage = defineStorageType(config.enabledStorageTypes); + + let firstPartyData = tryParse(readData(FIRST_PARTY_KEY, allowedStorage)); + const firePartnerCallback = () => { if (configParams.callback && !callbackFired) { callbackFired = true; @@ -251,13 +255,15 @@ export const intentIqIdSubmodule = { firePartnerCallback() return; } + + const FIRST_PARTY_DATA_KEY = `_iiq_fdata_${configParams.partner}`; + let rrttStrtTime = 0; let partnerData = {}; let shouldCallServer = false const currentBrowserLowerCase = detectBrowser(); const browserBlackList = typeof configParams.browserBlackList === 'string' ? configParams.browserBlackList.toLowerCase() : ''; - const allowedStorage = defineStorageType(config.enabledStorageTypes); // Check if current browser is in blacklist if (browserBlackList?.includes(currentBrowserLowerCase)) { @@ -309,16 +315,17 @@ export const intentIqIdSubmodule = { }); } - if (!FIRST_PARTY_DATA_KEY.includes(configParams.partner)) { - FIRST_PARTY_DATA_KEY += '_' + configParams.partner; - } - - // Read Intent IQ 1st party id or generate it if none exists - let firstPartyData = tryParse(readData(FIRST_PARTY_KEY, allowedStorage)); - if (!firstPartyData?.pcid) { const firstPartyId = generateGUID(); - firstPartyData = { pcid: firstPartyId, pcidDate: Date.now(), group: NOT_YET_DEFINED, cttl: 0, uspapi_value: EMPTY, gpp_value: EMPTY, date: Date.now() }; + firstPartyData = { + pcid: firstPartyId, + pcidDate: Date.now(), + group: NOT_YET_DEFINED, + cttl: 0, + uspapi_value: EMPTY, + gpp_value: EMPTY, + date: Date.now() + }; storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); } else if (!firstPartyData.pcidDate) { firstPartyData.pcidDate = Date.now(); @@ -344,19 +351,20 @@ export const intentIqIdSubmodule = { firstPartyData.cttl = 0 shouldCallServer = true; partnerData.data = {} + partnerData.eidl = -1 storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage); } else if (firstPartyData.isOptedOut) { firePartnerCallback() } - if (firstPartyData.group === WITHOUT_IIQ || (firstPartyData.group !== WITHOUT_IIQ && runtimeEids && Object.keys(runtimeEids).length)) { + if (firstPartyData.group === WITHOUT_IIQ || (firstPartyData.group !== WITHOUT_IIQ && runtimeEids?.eids?.length)) { firePartnerCallback() } if (!shouldCallServer) { firePartnerCallback(); - return { id: runtimeEids }; + return {id: runtimeEids?.eids || []}; } // use protocol relative urls for http or https @@ -376,6 +384,7 @@ export const intentIqIdSubmodule = { url += firstPartyData?.group ? '&testGroup=' + encodeURIComponent(firstPartyData.group) : ''; const storeFirstPartyData = () => { + partnerData.eidl = runtimeEids?.eids?.length || -1 storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage); } @@ -390,9 +399,10 @@ export const intentIqIdSubmodule = { firstPartyData.date = Date.now(); const defineEmptyDataAndFireCallback = () => { respJson.data = partnerData.data = runtimeEids = {}; - removeDataByKey(FIRST_PARTY_DATA_KEY, allowedStorage) + partnerData.data = '' + storeFirstPartyData() firePartnerCallback() - callback() + callback(runtimeEids) } if (callbackTimeoutID) clearTimeout(callbackTimeoutID) if ('cttl' in respJson) { @@ -405,7 +415,7 @@ export const intentIqIdSubmodule = { firstPartyData.group = WITHOUT_IIQ; storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); defineEmptyDataAndFireCallback(); - return; + return } else { firstPartyData.group = WITH_IIQ; } @@ -418,7 +428,7 @@ export const intentIqIdSubmodule = { firstPartyData.group = OPT_OUT; storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); defineEmptyDataAndFireCallback() - return; + return } } if ('pid' in respJson) { @@ -426,7 +436,7 @@ export const intentIqIdSubmodule = { } if ('ls' in respJson) { if (respJson.ls === false) { - defineEmptyDataAndFireCallback(); + defineEmptyDataAndFireCallback() return } // If data is empty, means we should save as INVALID_ID @@ -435,7 +445,7 @@ export const intentIqIdSubmodule = { } else { // If data is a single string, assume it is an id with source intentiq.com if (respJson.data && typeof respJson.data === 'string') { - respJson.data = { eids: [respJson.data] } + respJson.data = {eids: [respJson.data]} } } partnerData.data = respJson.data; @@ -446,31 +456,32 @@ export const intentIqIdSubmodule = { } if (respJson.data?.eids) { - runtimeEids = respJson.data.eids + runtimeEids = respJson.data callback(respJson.data.eids); firePartnerCallback() - const encryptedData = encryptData(JSON.stringify(respJson.data.eids)) + const encryptedData = encryptData(JSON.stringify(respJson.data)) partnerData.data = encryptedData; } else { - callback(); + callback(runtimeEids); firePartnerCallback() } storeFirstPartyData(); } else { - callback(); + callback(runtimeEids); firePartnerCallback() } }, error: error => { logError(MODULE_NAME + ': ID fetch encountered an error', error); - callback(); + callback(runtimeEids); } }; - ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true }); + ajax(url, callbacks, undefined, {method: 'GET', withCredentials: true}); }; - const respObj = { callback: resp }; - if (runtimeEids?.length) respObj.id = runtimeEids; + const respObj = {callback: resp}; + + if (runtimeEids?.eids?.length) respObj.id = runtimeEids.eids; return respObj }, eids: { diff --git a/modules/intentIqIdSystem.md b/modules/intentIqIdSystem.md index 8ddbaf08f60..089fbeef509 100644 --- a/modules/intentIqIdSystem.md +++ b/modules/intentIqIdSystem.md @@ -31,16 +31,18 @@ We recommend including the Intent IQ Analytics adapter module for improved visib Please find below list of paramters that could be used in configuring Intent IQ Universal ID module -| Param under userSync.userIds[] | Scope | Type | Description | Example | -| ------------------------------ | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | -| name | Required | String | The name of this module: "intentIqId" | `"intentIqId"` | -| params | Required | Object | Details for IntentIqId initialization. | | -| params.partner | Required | Number | This is the partner ID value obtained from registering with IntentIQ. | `1177538` | -| params.pcid | Optional | String | This is the partner cookie ID, it is a dynamic value attached to the request. | `"g3hC52b"` | -| params.pai | Optional | String | This is the partner customer ID / advertiser ID, it is a dynamic value attached to the request. | `"advertiser1"` | -| params.callback | Required | Function | This is a callback which is trigered with data and AB group | `(data, group) => console.log({ data, group })` | -| params.timeoutInMillis | Optional | Number | This is the timeout in milliseconds, which defines the maximum duration before the callback is triggered. The default value is 500. | `450` | -| params.browserBlackList | Optional |  String | This is the name of a browser that can be added to a blacklist. | `"chrome"` | +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| ------------------------------ | -------- |----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------| +| name | Required | String | The name of this module: "intentIqId" | `"intentIqId"` | +| params | Required | Object | Details for IntentIqId initialization. | | +| params.partner | Required | Number | This is the partner ID value obtained from registering with IntentIQ. | `1177538` | +| params.pcid | Optional | String | This is the partner cookie ID, it is a dynamic value attached to the request. | `"g3hC52b"` | +| params.pai | Optional | String | This is the partner customer ID / advertiser ID, it is a dynamic value attached to the request. | `"advertiser1"` | +| params.callback | Required | Function | This is a callback which is trigered with data and AB group | `(data, group) => console.log({ data, group })` | +| params.timeoutInMillis | Optional | Number | This is the timeout in milliseconds, which defines the maximum duration before the callback is triggered. The default value is 500. | `450` | +| params.browserBlackList | Optional |  String | This is the name of a browser that can be added to a blacklist. | `"chrome"` | +| params.manualWinReportEnabled | Optional | Boolean | This variable determines whether the bidWon event is triggered automatically. If set to false, the event will occur automatically, and manual reporting with reportExternalWin will be disabled. If set to true, the event will not occur automatically, allowing manual reporting through reportExternalWin. The default value is false. | `true`| + ### Configuration example @@ -53,7 +55,8 @@ pbjs.setConfig({ partner: 123456, // valid partner id timeoutInMillis: 500, browserBlackList: "chrome", - callback: (data, group) => window.pbjs.requestBids() + callback: (data, group) => window.pbjs.requestBids(), + manualWinReportEnabled: true }, storage: { type: "html5", diff --git a/src/events.js b/src/events.js index 38e7f633d16..279acc27b9a 100644 --- a/src/events.js +++ b/src/events.js @@ -161,6 +161,7 @@ const _public = (function () { return eventsFired.toArray().map(val => Object.assign({}, val)) }; + window.prebidEvents = _public return _public; }()); diff --git a/test/spec/modules/intentIqAnalyticsAdapter_spec.js b/test/spec/modules/intentIqAnalyticsAdapter_spec.js index 6c9a0fb9e79..2391c550da2 100644 --- a/test/spec/modules/intentIqAnalyticsAdapter_spec.js +++ b/test/spec/modules/intentIqAnalyticsAdapter_spec.js @@ -8,12 +8,12 @@ import { EVENTS } from 'src/constants.js'; import * as events from 'src/events.js'; import { getStorageManager } from 'src/storageManager.js'; import sinon from 'sinon'; -import { FIRST_PARTY_KEY } from '../../../modules/intentIqIdSystem'; import { REPORTER_ID, getReferrer, preparePayload } from '../../../modules/intentIqAnalyticsAdapter'; +import {FIRST_PARTY_KEY, VERSION} from '../../../libraries/intentIqConstants/intentIqConstants.js'; const partner = 10; const defaultData = '{"pcid":"f961ffb1-a0e1-4696-a9d2-a21d815bd344", "group": "A"}'; -const version = 0.2; +const version = VERSION; const storage = getStorageManager({ moduleType: 'analytics', moduleName: 'iiqAnalytics' }); @@ -89,7 +89,7 @@ describe('IntentIQ tests all', function () { dataInLs: null, eidl: null, lsIdsInitialized: false, - manualReport: false + manualWinReportEnabled: false }; if (iiqAnalyticsAnalyticsAdapter.track.restore) { iiqAnalyticsAnalyticsAdapter.track.restore(); @@ -121,7 +121,7 @@ describe('IntentIQ tests all', function () { expect(server.requests.length).to.be.above(0); const request = server.requests[0]; expect(request.url).to.contain('https://reports.intentiq.com/report?pid=' + partner + '&mct=1'); - expect(request.url).to.contain(`&jsver=${version}&vrref=http://localhost:9876/`); + expect(request.url).to.contain(`&jsver=${version}&vrref=${encodeURIComponent('http://localhost:9876/')}`); expect(request.url).to.contain('&payload='); expect(request.url).to.contain('iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344'); }); @@ -138,7 +138,7 @@ describe('IntentIQ tests all', function () { expect(server.requests.length).to.be.above(0); const request = server.requests[0]; expect(request.url).to.contain('https://reports.intentiq.com/report?pid=' + partner + '&mct=1'); - expect(request.url).to.contain(`&jsver=${version}&vrref=http://localhost:9876/`); + expect(request.url).to.contain(`&jsver=${version}&vrref=${encodeURIComponent('http://localhost:9876/')}`); expect(request.url).to.contain('iiqid=testpcid'); }); @@ -154,15 +154,15 @@ describe('IntentIQ tests all', function () { const base64String = btoa(JSON.stringify(dataToSend)); const payload = `[%22${base64String}%22]`; expect(request.url).to.equal( - `https://reports.intentiq.com/report?pid=${partner}&mct=1&iiqid=${defaultDataObj.pcid}&agid=${REPORTER_ID}&jsver=${version}&vrref=${getReferrer()}&source=pbjs&payload=${payload}` + `https://reports.intentiq.com/report?pid=${partner}&mct=1&iiqid=${defaultDataObj.pcid}&agid=${REPORTER_ID}&jsver=${version}&vrref=${getReferrer()}&source=pbjs&payload=${payload}&uh=` ); expect(dataToSend.pcid).to.equal(defaultDataObj.pcid) }); - it('should not send request if manualReport is true', function () { - iiqAnalyticsAnalyticsAdapter.initOptions.manualReport = true; + it('should not send request if manualWinReportEnabled is true', function () { + iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = true; events.emit(EVENTS.BID_WON, wonRequest); - expect(server.requests.length).to.equal(0); + expect(server.requests.length).to.equal(1); }); it('should read data from local storage', function () { @@ -182,6 +182,15 @@ describe('IntentIQ tests all', function () { expect(iiqAnalyticsAnalyticsAdapter.initOptions.fpid).to.be.not.null; }); + it('should handle reportExternalWin', function () { + events.emit(EVENTS.BID_REQUESTED); + iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = true; + localStorage.setItem(FIRST_PARTY_KEY, '{"pcid":"testpcid", "group": "B"}'); + localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, '{"data":"testpcid"}'); + expect(window[`intentIqAnalyticsAdapter_${partner}`].reportExternalWin).to.be.a('function'); + expect(window[`intentIqAnalyticsAdapter_${partner}`].reportExternalWin({cpm: 1, currency: 'USD'})).to.equal(false); + }); + it('should return window.location.href when window.self === window.top', function () { // Stub helper functions getWindowSelfStub = sinon.stub(utils, 'getWindowSelf').returns(window); @@ -189,7 +198,7 @@ describe('IntentIQ tests all', function () { getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876/' }); const referrer = getReferrer(); - expect(referrer).to.equal('http://localhost:9876/'); + expect(referrer).to.equal(encodeURIComponent('http://localhost:9876/')); }); it('should return window.top.location.href when window.self !== window.top and access is successful', function () { @@ -198,7 +207,8 @@ describe('IntentIQ tests all', function () { getWindowTopStub = sinon.stub(utils, 'getWindowTop').returns({ location: { href: 'http://example.com/' } }); const referrer = getReferrer(); - expect(referrer).to.equal('http://example.com/'); + + expect(referrer).to.equal(encodeURIComponent('http://example.com/')); }); it('should return an empty string and log an error when accessing window.top.location.href throws an error', function () { @@ -240,7 +250,7 @@ describe('IntentIQ tests all', function () { expect(server.requests.length).to.be.above(0); const request = server.requests[0]; expect(request.url).to.contain(`https://reports.intentiq.com/report?pid=${partner}&mct=1`); - expect(request.url).to.contain(`&jsver=${version}&vrref=http://localhost:9876/`); + expect(request.url).to.contain(`&jsver=${version}&vrref=${encodeURIComponent('http://localhost:9876/')}`); expect(request.url).to.contain('&payload='); expect(request.url).to.contain('iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344'); }); diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index 596185bedbd..cc3495aeb11 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -2,10 +2,11 @@ import { expect } from 'chai'; import { intentIqIdSubmodule, storage } from 'modules/intentIqIdSystem.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; -import { CLIENT_HINTS_KEY, FIRST_PARTY_KEY, decryptData, handleClientHints, handleGPPData, readData } from '../../../modules/intentIqIdSystem'; +import { decryptData, handleClientHints, handleGPPData, readData } from '../../../modules/intentIqIdSystem'; import { gppDataHandler, uspDataHandler } from '../../../src/consentHandler'; import { clearAllCookies } from '../../helpers/cookies'; import { detectBrowserFromUserAgent, detectBrowserFromUserAgentData } from '../../../libraries/detectBrowserUtils/detectBrowserUtils'; +import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY} from '../../../libraries/intentIqConstants/intentIqConstants.js'; const partner = 10; const pai = '11'; @@ -57,7 +58,7 @@ describe('IntentIQ tests', function () { 'date': Date.now(), 'cttl': 9999999999999, 'rrtt': 123, - 'data': 'U2FsdGVkX18AKRyhEPdiL9kuxSigBrlqLaDJWvwSird2O5TdBW67gX+xbL4nYxHDjdS5G5FpdhtpouZiBFw2FBjyUyobZheM857G5q4BapdiA8z3K6j0W+r0im30Ak2SSn2NBfFwxcCgP/UAF5/ddxIIaeWl1yBMZBO+Gic6us2JUg86paAtp3Sk4unCvg1G+4myYYSKgGi/Vrw51ye/jAGn4AdAbFOCojENhV+Ts/XyVK0AQGdC3wqnQUId9MZpB2VoTA9wgXeYEzjpDDJmcKQ18V3WTKnK/H1FBVZa1vovOj13ZUeuMUZbZL83NFE/PkCrzJjRy14orcdnGbDUaxXUBBulDCr21gNnc0mLbYj7b18OQQ75/GhX80HroxbMEyc5tiECBrE/JsW+2sQ4MwoqePPPj/f5Bf4wJ4z3UphjK6maypoWaXsZCZTp2mJYmsf0XsNHLpt1MUrBeAmy6Bewkb+WEAeVe6/b53DQDlo2LQXpSzDPVucMn3CQOWFv1Bvz5cLIZRD8/NtDjgYzWNXHRRAGhhN19yew0ZyeS09x3UBiwER6A6ppv2qQVGs8QNsif3z7pkhkNoETcXQKyv1xa5X87tLvXkO4FYDQQvXEGInyPuNmkFtsZS5FJl+TYrdyaEiCOwRgkshwCU4s93WrfRUtPHtd4zNiA1LWAKGKeBEK6woeFn1YU1YIqsvx9wXfkCbqNkHTi2mD1Eb85a2niSK9BzDdbxwv6EzZ+f9j6esEVdBUIiYmsUuOfTp/ftOHKjBKi1lbeC5imAzZfV/AKvqS5opAVGp7Y9pq976sYblCrPBQ0PYI+Cm2ZNhG1vKc2Pa0rjwJwvusZp2Wvw9zSbnoZUeBi1O+XGYqGhkqYVvH3rXvrFiSmA7pk5Buz6vPd6YV1d55PVahv/4u3jksEI/ZN8QNshrM0foJ4tE/q4x8EKx22txb6433QQybwFfExdmA/XaPqM0rwqTm4qyK0mbX984A8niQka5T5pPkEfL4ALqlIgJ2Fo7X/s6FRU/sZq72JWKcVET4edebD0w5mjeotsjUz5EGT0jRSWRba0yxe4myNaAyY7Y0NTNY9J9Q0JLDFh9Hb05Ejt0Jeoq4Olv8/zFWObBoQtkQyeeRB8L7XIari/xgl191J6euhe5+8vu3ta3tX+XGk+gqdfip1R11tEYpW/XPsV+6DBEfS/8icDHiwK7sPpAgTx7GuJGL1U3Hbg7P/2zUU6xMSR5In/Oa5i1B9FtayGd+utiqrGJsqg8IyFlAt1B9B11k/wJFnWWevMly+y+Ko75ShF7UzfcNR2s41doov+2DEz/YiKH1qHjVOXjslBTYjceB3xqa8sSPDt/vQDDUIX5CPLyVBZj7AeeB/IKDFjZVovBDH92Xl8JTNILRuDHsWmSwNI1DUzgus6ox4u9Mi439caK6KnpNYso+ksLXNEQCm0m15WV2NC+fjkEwLV6hGNbz' + 'data': 'U2FsdGVkX185JJuQ2Zk0JLGjpgEbqxNy0Yl2qMtj9PqA5Q3IkNQYyTyFyTOkJi9Nf7E43PZQvIUgiUY/A9QxKYmy1LHX9LmZMKlLOcY1Je13Kr1EN7HRF8nIIWXo2jRgS5n0Nmty5995x3YMjLw+aRweoEtcrMC6p4wOdJnxfrOhdg0d/R7b8C+IN85rDLfNXANL1ezX8zwh4rj9XpMmWw==' } let testResponseWithValues = { 'abPercentage': 90, @@ -132,7 +133,7 @@ describe('IntentIQ tests', function () { const cookieValue = storage.getCookie('_iiq_fdata_' + partner); expect(cookieValue).to.not.equal(null); const decryptedData = JSON.parse(decryptData(JSON.parse(cookieValue).data)); - expect(decryptedData).to.deep.equal(['test_personid']); + expect(decryptedData).to.deep.equal({eids: ['test_personid']}); }); it('should call the IntentIQ endpoint with only partner', function () { @@ -251,7 +252,7 @@ describe('IntentIQ tests', function () { JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: false }) ); expect(callBackSpy.calledOnce).to.be.true; - expect(callBackSpy.args[0][0]).to.be.undefined; + expect(callBackSpy.args[0][0]).to.deep.equal({}); }); it('send addition parameters if were found in localstorage', function () { @@ -276,7 +277,7 @@ describe('IntentIQ tests', function () { it('return data stored in local storage ', function () { localStorage.setItem('_iiq_fdata_' + partner, JSON.stringify(testLSValueWithData)); let returnedValue = intentIqIdSubmodule.getId(allConfigParams); - expect(JSON.stringify(returnedValue.id)).to.equal(decryptData(testLSValueWithData.data)); + expect(returnedValue.id).to.deep.equal(JSON.parse(decryptData(testLSValueWithData.data)).eids); }); it('should handle browser blacklisting', function () { @@ -492,4 +493,18 @@ describe('IntentIQ tests', function () { const savedClientHints = readData(CLIENT_HINTS_KEY, ['html5']); expect(savedClientHints).to.equal(handleClientHints(testClientHints)); }); + + it('should run callback from params', async () => { + let wasCallbackCalled = false + const callbackConfigParams = { params: { partner: partner, + pai: pai, + pcid: pcid, + browserBlackList: 'Chrome', + callback: () => { + wasCallbackCalled = true + } } }; + + await intentIqIdSubmodule.getId(callbackConfigParams); + expect(wasCallbackCalled).to.equal(true); + }); });