Skip to content

Commit

Permalink
IntentIq ID & Analytics Modules : CMP values and browser detection bu…
Browse files Browse the repository at this point in the history
…g fix (#12511)

* update intentIqAnalyticsAdapter.js && intentIqIdSystem.js

* fix lint issues

* fix tests

* move info

* resolve issues

* update storeFirstPartyData

* remove unused code

* update defineEmptyDataAndFireCallback

* update fix lint

* update reportExternalWin

* small fixes

* update test && add docs

* AGT-347: Support domain name

* AGT-347: Support domain name

* AGT-374: Support domainName to vrref

* AGT-374: tests in progress

* AGT-374: Remove duplicate encoded in getRelevantRefferer and fix tests

* AGT-374: Add test domainName, changes in documentation

* AGT-374: Change js version value

* AGT-374: Remove extra coma

* Remove unused method

* AGT-384: gpp string value

* AGT-384: GPC value, browserDetector fix

* AGT-384: Remove gpc logic

* AGT-384: Reduce getGppValue method

* AGT-384: Gpp tests and prevent send ids from LS in group B

* AGT-384: Change empty array of eids

* AGT-384: Some fixes after review

---------

Co-authored-by: dlepetynskyi <[email protected]>
Co-authored-by: DimaIntentIQ <[email protected]>
Co-authored-by: DimaIntentIQ <[email protected]>
  • Loading branch information
4 people authored Nov 27, 2024
1 parent 36f60c1 commit 8056514
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 104 deletions.
2 changes: 1 addition & 1 deletion libraries/intentIqConstants/intentIqConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ 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.22
export const VERSION = 0.23
10 changes: 10 additions & 0 deletions libraries/intentIqUtils/detectBrowserUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ export function detectBrowserFromUserAgent(userAgent) {
ie: /MSIE|Trident/,
};

// Check for Edge first
if (browserRegexPatterns.edge.test(userAgent)) {
return 'edge';
}

// Check for Opera next
if (browserRegexPatterns.opera.test(userAgent)) {
return 'opera';
}

// Check for Chrome first to avoid confusion with Safari
if (browserRegexPatterns.chrome.test(userAgent)) {
return 'chrome';
Expand Down
16 changes: 16 additions & 0 deletions libraries/intentIqUtils/getGppValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {gppDataHandler} from '../../src/consentHandler.js';

/**
* Retrieves the GPP string value and additional GPP-related information.
* This function extracts the GPP data, encodes it, and determines specific GPP flags such as GPI and applicable sections.
* @return {Object} An object containing:
* - `gppString` (string): The encoded GPP string value.
* - `gpi` (number): An indicator representing whether GPP consent is available (0 if available, 1 if not).
*/
export function getGppValue() {
const gppData = gppDataHandler.getConsentData();
const gppString = gppData?.gppString || '';
const gpi = gppString ? 0 : 1;

return { gppString, gpi };
}
6 changes: 5 additions & 1 deletion modules/intentIqAnalyticsAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {EVENTS} from '../src/constants.js';
import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js';
import {detectBrowser} from '../libraries/intentIqUtils/detectBrowserUtils.js';
import {appendVrrefAndFui, getReferrer} from '../libraries/intentIqUtils/getRefferer.js';
import {getGppValue} from '../libraries/intentIqUtils/getGppValue.js';
import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY, VERSION} from '../libraries/intentIqConstants/intentIqConstants.js';

const MODULE_NAME = 'iiqAnalytics'
Expand Down Expand Up @@ -264,6 +265,7 @@ function constructFullUrl(data) {
let report = [];
data = btoa(JSON.stringify(data));
report.push(data);
const gppData = getGppValue();

let url = defaultUrl + '?pid=' + iiqAnalyticsAnalyticsAdapter.initOptions.partner +
'&mct=1' +
Expand All @@ -273,7 +275,9 @@ function constructFullUrl(data) {
'&jsver=' + VERSION +
'&source=pbjs' +
'&payload=' + JSON.stringify(report) +
'&uh=' + iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints;
'&uh=' + iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints +
(gppData.gppString ? '&gpp=' + encodeURIComponent(gppData.gppString) : '');

url = appendVrrefAndFui(url, iiqAnalyticsAnalyticsAdapter.initOptions.domainName);
return url;
}
Expand Down
51 changes: 15 additions & 36 deletions modules/intentIqIdSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ 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 {uspDataHandler} from '../src/consentHandler.js';
import AES from 'crypto-js/aes.js';
import Utf8 from 'crypto-js/enc-utf8.js';
import {detectBrowser} from '../libraries/intentIqUtils/detectBrowserUtils.js';
import {appendVrrefAndFui} from '../libraries/intentIqUtils/getRefferer.js';
import {getGppValue} from '../libraries/intentIqUtils/getGppValue.js';
import {
FIRST_PARTY_KEY,
WITH_IIQ, WITHOUT_IIQ,
Expand Down Expand Up @@ -158,22 +159,6 @@ function tryParse(data) {
}
}

/**
* Convert GPP data to an object
* @param {Object} data
* @return {string} The JSON string representation of the input data.
*/
export function handleGPPData(data = {}) {
if (Array.isArray(data)) {
let obj = {};
for (const element of data) {
obj = Object.assign(obj, element);
}
return JSON.stringify(obj);
}
return JSON.stringify(data);
}

/**
* Processes raw client hints data into a structured format.
* @param {object} clientHints - Raw client hints data
Expand Down Expand Up @@ -231,17 +216,19 @@ export const intentIqIdSubmodule = {
getId(config) {
const configParams = (config?.params) || {};
let decryptedData, callbackTimeoutID;
let callbackFired = false
let runtimeEids = {}
let callbackFired = false;
let runtimeEids = { eids: [] };

const allowedStorage = defineStorageType(config.enabledStorageTypes);

let firstPartyData = tryParse(readData(FIRST_PARTY_KEY, allowedStorage));
const isGroupB = firstPartyData?.group === WITHOUT_IIQ;

const firePartnerCallback = () => {
if (configParams.callback && !callbackFired) {
callbackFired = true;
if (callbackTimeoutID) clearTimeout(callbackTimeoutID);
if (isGroupB) runtimeEids = { eids: [] };
configParams.callback(runtimeEids, firstPartyData?.group || NOT_YET_DEFINED);
}
}
Expand Down Expand Up @@ -276,22 +263,14 @@ export const intentIqIdSubmodule = {
// Get consent information
const cmpData = {};
const uspData = uspDataHandler.getConsentData();
const gppData = gppDataHandler.getConsentData();
const gppData = getGppValue();

if (uspData) {
cmpData.us_privacy = uspData;
}

if (gppData) {
cmpData.gpp = '';
cmpData.gpi = 1;

if (gppData.parsedSections && 'usnat' in gppData.parsedSections) {
cmpData.gpp = handleGPPData(gppData.parsedSections['usnat']);
cmpData.gpi = 0
}
cmpData.gpp_sid = gppData.applicableSections;
}
cmpData.gpp = gppData.gppString;
cmpData.gpi = gppData.gpi;

// Read client hints from storage
let clientHints = readData(CLIENT_HINTS_KEY, allowedStorage);
Expand Down Expand Up @@ -345,9 +324,9 @@ export const intentIqIdSubmodule = {
}
}

if (!firstPartyData.cttl || Date.now() - firstPartyData.date > firstPartyData.cttl || firstPartyData.uspapi_value !== cmpData.us_privacy || firstPartyData.gpp_value !== cmpData.gpp) {
if (!firstPartyData.cttl || Date.now() - firstPartyData.date > firstPartyData.cttl || firstPartyData.uspapi_value !== cmpData.us_privacy || firstPartyData.gpp_string_value !== cmpData.gpp) {
firstPartyData.uspapi_value = cmpData.us_privacy;
firstPartyData.gpp_value = cmpData.gpp;
firstPartyData.gpp_string_value = cmpData.gpp;
firstPartyData.isOptedOut = false
firstPartyData.cttl = 0
shouldCallServer = true;
Expand All @@ -364,8 +343,9 @@ export const intentIqIdSubmodule = {
}

if (!shouldCallServer) {
if (isGroupB) runtimeEids = { eids: [] };
firePartnerCallback();
return {id: runtimeEids?.eids || []};
return { id: runtimeEids.eids };
}

// use protocol relative urls for http or https
Expand All @@ -378,7 +358,7 @@ export const intentIqIdSubmodule = {
url += (partnerData.rrtt) ? '&rrtt=' + encodeURIComponent(partnerData.rrtt) : '';
url += firstPartyData.pcidDate ? '&iiqpciddate=' + encodeURIComponent(firstPartyData.pcidDate) : '';
url += cmpData.us_privacy ? '&pa=' + encodeURIComponent(cmpData.us_privacy) : '';
url += cmpData.gpp ? '&gpv=' + encodeURIComponent(cmpData.gpp) : '';
url += cmpData.gpp ? '&gpp=' + encodeURIComponent(cmpData.gpp) : '';
url += cmpData.gpi ? '&gpi=' + cmpData.gpi : '';
url += clientHints ? '&uh=' + encodeURIComponent(clientHints) : '';
url += VERSION ? '&jsver=' + VERSION : '';
Expand All @@ -402,8 +382,7 @@ export const intentIqIdSubmodule = {
partnerData.date = Date.now();
firstPartyData.date = Date.now();
const defineEmptyDataAndFireCallback = () => {
respJson.data = partnerData.data = runtimeEids = {};
partnerData.data = ''
respJson.data = partnerData.data = runtimeEids = { eids: [] };
storeFirstPartyData()
firePartnerCallback()
callback(runtimeEids)
Expand Down
118 changes: 52 additions & 66 deletions test/spec/modules/intentIqIdSystem_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ 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 { decryptData, handleClientHints, handleGPPData, readData } from '../../../modules/intentIqIdSystem';
import { decryptData, handleClientHints, readData } from '../../../modules/intentIqIdSystem';
import {getGppValue} from '../../../libraries/intentIqUtils/getGppValue.js';
import { gppDataHandler, uspDataHandler } from '../../../src/consentHandler';
import { clearAllCookies } from '../../helpers/cookies';
import { detectBrowserFromUserAgent, detectBrowserFromUserAgentData } from '../../../libraries/intentIqUtils/detectBrowserUtils';
Expand Down Expand Up @@ -252,7 +253,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.deep.equal({});
expect(callBackSpy.args[0][0]).to.deep.equal({eids: []});
});

it('send addition parameters if were found in localstorage', function () {
Expand Down Expand Up @@ -306,36 +307,39 @@ describe('IntentIQ tests', function () {
expect(logErrorStub.called).to.be.true;
});

describe('handleGPPData', function () {
it('should convert array of objects to a single JSON string', function () {
const input = [
{ key1: 'value1' },
{ key2: 'value2' }
];
const expectedOutput = JSON.stringify({ key1: 'value1', key2: 'value2' });
const result = handleGPPData(input);
expect(result).to.equal(expectedOutput);
});

it('should convert a single object to a JSON string', function () {
const input = { key1: 'value1', key2: 'value2' };
const expectedOutput = JSON.stringify(input);
const result = handleGPPData(input);
expect(result).to.equal(expectedOutput);
});

it('should handle empty object', function () {
const input = {};
const expectedOutput = JSON.stringify(input);
const result = handleGPPData(input);
expect(result).to.equal(expectedOutput);
});

it('should handle empty array', function () {
const input = [];
const expectedOutput = JSON.stringify({});
const result = handleGPPData(input);
expect(result).to.equal(expectedOutput);
describe('getGppValue', function () {
const testCases = [
{
description: 'should return gppString and gpi=0 when GPP data exists',
input: { gppString: '{"key1":"value1","key2":"value2"}' },
expectedOutput: { gppString: '{"key1":"value1","key2":"value2"}', gpi: 0 }
},
{
description: 'should return empty gppString and gpi=1 when GPP data does not exist',
input: null,
expectedOutput: { gppString: '', gpi: 1 }
},
{
description: 'should return empty gppString and gpi=1 when gppString is not set',
input: {},
expectedOutput: { gppString: '', gpi: 1 }
},
{
description: 'should handle GPP data with empty string',
input: { gppString: '' },
expectedOutput: { gppString: '', gpi: 1 }
}
];

testCases.forEach(({ description, input, expectedOutput }) => {
it(description, function () {
sinon.stub(gppDataHandler, 'getConsentData').returns(input);

const result = getGppValue();
expect(result).to.deep.equal(expectedOutput);

gppDataHandler.getConsentData.restore();
});
});
});

Expand Down Expand Up @@ -431,54 +435,36 @@ describe('IntentIQ tests', function () {
expect(firstPartyData.uspapi_value).to.equal(uspData);
});

it('should set cmpData.gpp and cmpData.gpp_sid if gppData exists and has parsedSections with usnat', function () {
const gppData = {
parsedSections: {
usnat: { key1: 'value1', key2: 'value2' }
},
applicableSections: ['usnat']
it('should create a request with gpp data if gppData exists and has gppString', function () {
const mockGppValue = {
gppString: '{"key1":"value1","key2":"value2"}',
gpi: 0
};
gppDataHandlerStub.returns(gppData);

let callBackSpy = sinon.spy();
let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback;
submoduleCallback(callBackSpy);
let request = server.requests[0];
expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid=');
request.respond(
200,
responseHeader,
JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: false, isOptedOut: false })
);
expect(callBackSpy.calledOnce).to.be.true;

const firstPartyData = JSON.parse(localStorage.getItem(FIRST_PARTY_KEY));
expect(firstPartyData.gpp_value).to.equal(JSON.stringify({ key1: 'value1', key2: 'value2' }));
});

it('should handle gppData without usnat in parsedSections', function () {
const gppData = {
parsedSections: {
euconsent: { key1: 'value1' }
},
applicableSections: ['euconsent']
const mockConfig = {
params: { partner: partner },
enabledStorageTypes: ['localStorage']
};
gppDataHandlerStub.returns(gppData);

gppDataHandlerStub.returns(mockGppValue);

let callBackSpy = sinon.spy();
let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback;
let submoduleCallback = intentIqIdSubmodule.getId(mockConfig).callback;
submoduleCallback(callBackSpy);

let request = server.requests[0];
expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid=');

request.respond(
200,
responseHeader,
JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: false, isOptedOut: true })
JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: true })
);

expect(request.url).to.contain(`&gpp=${encodeURIComponent(mockGppValue.gppString)}`);
expect(callBackSpy.calledOnce).to.be.true;

const firstPartyData = JSON.parse(localStorage.getItem(FIRST_PARTY_KEY));
expect(firstPartyData.gpp_value).to.equal('');
expect(firstPartyData.gpp_string_value).to.equal(mockGppValue.gppString);
});
});

Expand Down

0 comments on commit 8056514

Please sign in to comment.