Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
};
Original file line number Diff line number Diff line change
Expand Up @@ -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) + '.';
Expand All @@ -21,11 +21,11 @@
* @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;
}
}

Check failure on line 31 in libraries/intentIqUtils/cryptionUtils.ts

View workflow job for this annotation

GitHub Actions / Run linter

Newline required at end of file but not found

Check failure on line 31 in libraries/intentIqUtils/cryptionUtils.ts

View workflow job for this annotation

GitHub Actions / Run linter

Newline required at end of file but not found
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,23 @@
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
* clampPct(150) => 100
* 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));
Expand All @@ -24,16 +32,18 @@
* @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;
}

/**
Expand All @@ -47,28 +57,32 @@
* @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;
}
return IIQServerConfigurationSource(tc, configObject.abPercentage);
}
}
}
}

Check failure on line 88 in libraries/intentIqUtils/defineABTestingGroupUtils.ts

View workflow job for this annotation

GitHub Actions / Run linter

Newline required at end of file but not found

Check failure on line 88 in libraries/intentIqUtils/defineABTestingGroupUtils.ts

View workflow job for this annotation

GitHub Actions / Run linter

Newline required at end of file but not found
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -22,8 +31,8 @@
* @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<string, RegExp> = {
opera: /Opera|OPR/,
edge: /Edg/,
chrome: /Chrome|CriOS/,
Expand All @@ -48,14 +57,17 @@
}

// 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;
}
}

Expand All @@ -67,16 +79,20 @@
* @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';
}
}

Check failure on line 98 in libraries/intentIqUtils/detectBrowserUtils.ts

View workflow job for this annotation

GitHub Actions / Run linter

Newline required at end of file but not found

Check failure on line 98 in libraries/intentIqUtils/detectBrowserUtils.ts

View workflow job for this annotation

GitHub Actions / Run linter

Newline required at end of file but not found
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
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<string, any>) => void
): void {
try {
if (!gamObjectReference || !sendData) {
logError('Failed to get gamPredictionReport, required data is missed');
return;
}
const getSlotTargeting = (slot) => {

const getSlotTargeting = (slot: any): Record<string, string[]> => {
try {
return getSlotTargetingMap(slot);
} catch (e) {
Expand All @@ -17,11 +21,11 @@
}
};

const extractWinData = (gamEvent) => {
const extractWinData = (gamEvent: any): Record<string, any> | undefined => {
const slot = gamEvent.slot;
const targeting = getSlotTargeting(slot);

const dataToSend = {
const dataToSend: Record<string, any> = {
placementId: slot.getSlotElementId && slot.getSlotElementId(),
adUnitPath: slot.getAdUnitPath && slot.getAdUnitPath(),
bidderCode: targeting.hb_bidder ? targeting.hb_bidder[0] : null,
Expand All @@ -30,70 +34,88 @@

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);
}
};
}

Check failure on line 121 in libraries/intentIqUtils/gamPredictionReport.ts

View workflow job for this annotation

GitHub Actions / Run linter

Newline required at end of file but not found

Check failure on line 121 in libraries/intentIqUtils/gamPredictionReport.ts

View workflow job for this annotation

GitHub Actions / Run linter

Newline required at end of file but not found
Loading
Loading