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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions libraries/intentIqConstants/performanceConstants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
export const PERFORMANCE_EVENT = {
SET_IIQ_OBJECT: 1,
SET_USER_CONFIG: 2,
BROWSER_BLACKLIST_DETECTED: 3,
SET_DEFAULT_VALUES: 6,
INIT_COUNTERS: 7,
INIT_STORAGE: 8,
READ_FIRST_PARTY_DATA: 11,
GENERATED_NEW_PCID: 12,
DEFINE_FIRST_PARTY_DATA: 13,
READ_CLIENT_HINTS_FROM_LS: 14,
READ_TCF: 16,
RECEIVED_TCF: 17,
CREATE_REQUEST_URL: 20,
MAKE_VR_REQUEST: 21,
READ_USP_START: 25,
READ_USP_END: 26,
START_GET_CLIENT_HINTS: 27,
RECEIVED_CLIENT_HINTS: 28,
GROUP_DEFINING: 29,
VR_RESPONSE_RECEIVED: 38,
CALLBACK_FIRE: 39,
READ_PARTNER_DATA_FROM_LS: 40,
REFRESH_USER_IDS_START: 41,
USER_IDS_SET_IN_PREBID: 42,
BROWSER_DETECTED: 43,
DEFINE_GROUP_FROM_SERVER: 45,
START_READ_LS_ENCODED_DATA: 46,
END_READ_LS_ENCODED_DATA: 47,
GET_DEVICE_INFO: 48,
SYNC_REQUEST: 49,
PROVIDED_GDPR: 51,
PROVIDED_USP: 53,
TIMEOUT_VS_VR_RESPONSE: 56,
TIME_SINCE_LAST_VR_REQUEST: 57,
ANALYTICS_ADAPTER_DETECTED: 61,
VR_RESPONSE_FINISHED: 62,
DEFINE_GROUP_BASED_ON_SERVER_RESPONSE: 63,
GAM_TARGETING_SET: 64,
CH_FETCH_FAILED: 65,
VR_CALLBACK_TIMEOUT: 66,
VR_REQUEST_SKIPPED: 67,
EIDS_FOUND_IN_LS: 68,
EIDS_FOUND_IN_RESPONSE: 69,
STORE_FIRST_PARTY_DATA: 70,
STORE_PARTNER_DATA: 71,
UPDATE_GLOBAL_OBJECT: 72,
BUILD_SYNC_PIXEL_URL: 73,
SEND_SYNC_REQUEST: 74,
ANALYTICS_REPORT_SENT: 75,
ANALYTICS_REPORT_FAILED: 76,
PREPARE_ANALYTICS_PAYLOAD: 77,
CONSTRUCT_ANALYTICS_URL: 78,
BID_WON_TRIGGERED: 79,
ENABLE_ANALYTICS: 80,
DISABLE_ANALYTICS: 81
};

export const ERROR_CODES = {
ALL: -1,
VR_BAD_RESPONSE: 0,
VR_REQUEST_TIMEOUT: 1,
MULTIPLE_VR_REQUESTS: 6,
MULTIPLE_VR_RESPONSES: 7,
CALLBACK_TRIGGERED_FEW_TIMES: 8,
CONFIG_OBJECT_INVALID: 9,
PARTNER_ID_NOT_PROVIDED: 10,
ANALYTICS_ADAPTER_MISSING: 11,
CONFIG_OBJECT_NOT_PROVIDED_CORRECTLY: 12,
CH_FETCH_FAILED: 13,
GAM_TARGETING_FAILED: 14,
ENCRYPTION_FAILED: 15,
DECRYPTION_FAILED: 16,
STORE_DATA_FAILED: 17,
READ_DATA_FAILED: 18,
INVALID_RESPONSE_DATA: 19,
ANALYTICS_REPORT_FAILED: 20,
ANALYTICS_PAYLOAD_FAILED: 21,
DUPLICATE_ANALYTICS_REPORT: 22
};

export const WARNING_CODES = {
ALL: -1,
CMP_TRIGGERED_FEW_TIMES: 0,
MULTIPLE_AGENTS_SAME_ID: 1,
AUCTION_EIDS_DISCREPANCY: 2,
AUCTION_STARTED_BEFORE_AGENT: 3,
CALLBACK_TIMEOUT_REACHED: 4,
EMPTY_EIDS_RESPONSE: 5,
USING_LS_EIDS: 6,
VR_SKIPPED: 7,
ANALYTICS_DISABLED: 8
};

export const ADDITIONAL_ANALYTIC_PARAMS = {
ALL: -1,
RUNNING_IN_TOP: 0,
LS_ACCESS: 1,
GAM_PRESENT: 2,
HAVE_EIDS_IN_LS: 3,
AUCTION_EIDS_DIFF: 4,
AUCTION_TO_BIDWON_PERCENTAGE: 5,
BIDWON_MINUS_IMPRESSIONS: 6,
CALLBACK_WITH_EIDS: 7,
CH_SUPPORTED: 8,
GDPR_APPLIES: 9,
USP_APPLIES: 10,
GPP_APPLIES: 11,
SERVER_CALLED: 12,
RESPONSE_HAD_EIDS: 13,
RESPONSE_HAD_PID: 14,
USER_OPTED_OUT: 15,
GAM_OBJECT_PRESENT: 16,
ANALYTICS_ENABLED: 17,
MANUAL_REPORTING_ENABLED: 18
};

export const ErrorType = {
ERROR: 'ed',
WARNING: 'ld',
FATAL: 'fd'
};

export const emptyWarning = {
[ErrorType.ERROR]: {},
[ErrorType.WARNING]: {},
[ErrorType.FATAL]: {}
};

export const edErrors = Object.values(ERROR_CODES).filter(id => id !== ERROR_CODES.ALL);
export const ldErrors = Object.values(WARNING_CODES).filter(id => id !== WARNING_CODES.ALL);
export const fdErrors = edErrors;

export const ErrorsMap = {
[ErrorType.ERROR]: edErrors,
[ErrorType.WARNING]: ldErrors,
[ErrorType.FATAL]: fdErrors
};

export const multipleErrors = [
{
id: PERFORMANCE_EVENT.MAKE_VR_REQUEST,
warningCode: ERROR_CODES.MULTIPLE_VR_REQUESTS,
errorType: ErrorType.ERROR
},
{
id: PERFORMANCE_EVENT.VR_RESPONSE_RECEIVED,
warningCode: ERROR_CODES.MULTIPLE_VR_RESPONSES,
errorType: ErrorType.ERROR
},
{
id: PERFORMANCE_EVENT.CALLBACK_FIRE,
warningCode: ERROR_CODES.CALLBACK_TRIGGERED_FEW_TIMES,
errorType: ErrorType.ERROR
}
];

export const businessAdapters = {
0: 'liveIntentAnalyticsAdapter',
1: 'euidIdSystem',
2: 'id5AnalyticsAdapter',
3: 'lotamePanoramaIdSystem',
4: 'identityLinkIdSystem',
5: 'merkleIdSystem',
6: 'fabrickIdSystem'
};
136 changes: 136 additions & 0 deletions libraries/intentIqUtils/performanceTracker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import {
ADDITIONAL_ANALYTIC_PARAMS as AAP,
businessAdapters,
emptyWarning,
ErrorsMap,
ErrorType,
multipleErrors
} from '../intentIqConstants/performanceConstants.js';

class PerformanceTracker {
constructor() {
this.performanceStart = performance.now();
this.performanceData = {};
this.warningData = JSON.parse(JSON.stringify(emptyWarning));
this.additionalAnalyticParams = {};
}

addPerformance(id, value) {
const raw = typeof value === 'number'
? value
: id === 0
? this.performanceStart
: id === 60
? performance.now()
: performance.now() - this.performanceStart;

this.performanceData[id] = this.performanceData[id] || [];
this.performanceData[id].push(parseFloat(raw.toFixed(1)));
}

addAdditionalAnalyticParam(id, value) {
const rawIds = [
AAP.AUCTION_EIDS_DIFF,
AAP.AUCTION_TO_BIDWON_PERCENTAGE,
AAP.BIDWON_MINUS_IMPRESSIONS
];

this.additionalAnalyticParams[id] = rawIds.includes(id) ? value : value ? 1 : 0;
}

addWarning(id, errorType) {
if (!ErrorsMap[errorType]?.includes(id)) return;
this.warningData[errorType][id] = (this.warningData[errorType][id] || 0) + 1;
}

checkWarnings(config = []) {
config.forEach(({ id, warningCode, errorType }) => {
if (this.performanceData[id]?.length > 1) this.addWarning(warningCode, errorType);
});
}

filterWarningData(data, ids = []) {
return ids.reduce((acc, id) => {
if (data[id]) acc[id] = data[id];
return acc;
}, {});
}

updatePerformanceData(ids = []) {
if (ids.includes(-1)) return { ...this.performanceData };
return ids.reduce((acc, id) => {
if (this.performanceData[id]) acc[id] = this.performanceData[id];
return acc;
}, {});
}

getFilteredData(pd, installedModules = [], force = false) {
this.checkWarnings(multipleErrors);

const performanceData = force ? { ...this.performanceData } : {};
const warningData = JSON.parse(JSON.stringify(emptyWarning));
const businessData = {};
let additionalAnalyticData = {};

if (!force && pd?.p?.ct && pd?.p?.d?.length) {
Object.assign(performanceData, this.updatePerformanceData(pd.p.d));
pd.p.ct--;
}

if (force) {
Object.assign(warningData.ed, this.warningData.ed);
Object.assign(warningData.ld, this.warningData.ld);
Object.assign(warningData.fd, this.warningData.fd);
additionalAnalyticData = { ...this.additionalAnalyticParams };
} else if (pd?.w?.ct) {
Object.values(ErrorType).forEach(key => {
const ids = pd?.w?.[key] || [];
warningData[key] = ids.includes(-1) ? this.warningData[key] : this.filterWarningData(this.warningData[key], ids);
});

if (Object.values(warningData).some(data => Object.keys(data).length)) pd.w.ct--;
}

if (pd?.b?.ct && pd?.b?.d?.length) {
const ids = pd.b.d.includes(-1) ? Object.keys(businessAdapters).map(Number) : pd.b.d;
ids.forEach(id => {
if (businessAdapters[id]) businessData[id] = installedModules.includes(businessAdapters[id]) ? 1 : 0;
});
pd.b.ct--;
}

if (!force && pd?.ad?.ct && pd?.ad?.d?.length) {
const keys = Object.keys(this.additionalAnalyticParams);
if (keys.length) {
additionalAnalyticData = pd.ad.d.includes(-1)
? { ...this.additionalAnalyticParams }
: pd.ad.d.reduce((acc, key) => {
acc[key] = this.additionalAnalyticParams[key];
return acc;
}, {});
pd.ad.ct--;
}
}

return {
apd: performanceData,
aed: warningData.ed,
ald: warningData.ld,
afd: warningData.fd,
abd: businessData,
add: additionalAnalyticData
};
}

hasPerformance(id) {
return this.performanceData[id] !== undefined;
}

resetData() {
this.performanceData = {};
this.warningData = JSON.parse(JSON.stringify(emptyWarning));
this.additionalAnalyticParams = {};
}
}

export const performanceTracker = new PerformanceTracker();
88 changes: 88 additions & 0 deletions libraries/intentIqUtils/performanceUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import adapterManager from '../../src/adapterManager.js';
import { getGlobal } from '../../src/prebidGlobal.js';

import {
PERFORMANCE_EVENT,
ERROR_CODES,
ErrorType,
ADDITIONAL_ANALYTIC_PARAMS
} from '../intentIqConstants/performanceConstants.js';

import { performanceTracker } from './performanceTracker.js';
import { appendVrrefAndFui } from './getRefferer.js';
import { getIiqServerAddress } from './intentIqConfig.js';
import { VERSION } from '../intentIqConstants/intentIqConstants.js';

const pbjs = getGlobal();

let missingAnalyticsReported = false;

export function hasIIQAnalytics(partnerId) {
try {
const wrapper = adapterManager.getAnalyticsAdapter('iiqAnalytics');
const enabled = wrapper?.adapter?.enabled === true;
const globalExists = !!window[`intentIqAnalyticsAdapter_${partnerId}`];
return enabled || globalExists;
} catch (e) {
return false;
}
}

export function collectPerformanceData({ configParams, firstPartyData, partnerData, runtimeEids, force = false }) {
const payload = performanceTracker.getFilteredData(partnerData?.pd, pbjs?.installedModules || [], force);
const hasPayload = Object.values(payload).some(data => data && typeof data === 'object' && Object.keys(data).length);

if (!hasPayload) return {};

return {
pcid: firstPartyData?.pcid,
dpi: configParams.partner,
jsver: configParams.version || VERSION,
pbjsver: pbjs?.version,
pid: firstPartyData?.pid,
vrref: appendVrrefAndFui('', configParams.domainName).replace('&vrref=', ''),
eidLn: runtimeEids?.eids?.length || 0,
dm: partnerData?.dm || [],
...payload
};
}

export function makePerformanceRequest({ configParams, firstPartyData, partnerData, runtimeEids, force = false }) {
const performanceData = collectPerformanceData({ configParams, firstPartyData, partnerData, runtimeEids, force });
if (!Object.keys(performanceData).length) return false;

const queryParams = new URLSearchParams();

Object.entries(performanceData).forEach(([key, value]) => {
if (value === undefined || value === null) return;
queryParams.append(key, typeof value === 'object' ? JSON.stringify(value) : String(value));
});

const endpoint = partnerData?.ae || `${getIiqServerAddress(configParams)}/report`;
new Image().src = `${endpoint}?${queryParams.toString()}`;

performanceTracker.addPerformance(PERFORMANCE_EVENT.ANALYTICS_REPORT_SENT);
performanceTracker.resetData();
return true;
}

export function scheduleAnalyticsDetection({ configParams, firstPartyData, partnerData, runtimeEids, timeout = 5000 }) {
const detect = () => {
if (missingAnalyticsReported) return;

const analyticsDetected = hasIIQAnalytics(configParams.partner);
performanceTracker.addAdditionalAnalyticParam(ADDITIONAL_ANALYTIC_PARAMS.ANALYTICS_ENABLED, analyticsDetected);

if (analyticsDetected) {
performanceTracker.addPerformance(PERFORMANCE_EVENT.ANALYTICS_ADAPTER_DETECTED);
return;
}

missingAnalyticsReported = true;
performanceTracker.addWarning(ERROR_CODES.ANALYTICS_ADAPTER_MISSING, ErrorType.ERROR);

makePerformanceRequest({ configParams, firstPartyData, partnerData, runtimeEids, force: true });
};

setTimeout(detect, timeout);
}
Loading
Loading