Skip to content

Commit

Permalink
Intent IQ ID Systems: first party updates (prebid#6618)
Browse files Browse the repository at this point in the history
* New features in iiq

* intentIqIdSystem.js updated logic & tests

* Tests fix

* api update

* tests update
  • Loading branch information
yuvalgg authored and stsepelin committed May 28, 2021
1 parent 9e3f36e commit 1a76585
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 30 deletions.
129 changes: 114 additions & 15 deletions modules/intentIqIdSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,111 @@
import * as utils from '../src/utils.js'
import {ajax} from '../src/ajax.js';
import {submodule} from '../src/hook.js'
import {getStorageManager} from '../src/storageManager.js';

const PCID_EXPIRY = 365;

const MODULE_NAME = 'intentIqId';
export const FIRST_PARTY_KEY = '_iiq_fdata';

export const storage = getStorageManager(undefined, MODULE_NAME);

const NOT_AVAILABLE = 'NA';

/**
* Verify the id is valid - Id value or Not Found (ignore not available response)
* @param id
* @returns {boolean|*|boolean}
* Verify the response is valid - Id value or Not Found (ignore not available response)
* @param response
* @param respJson - parsed json response
* @returns {boolean}
*/
function isValidId(id) {
return id && id != '' && id != NOT_AVAILABLE && isValidResponse(id);
function isValidResponse(response, respJson) {
if (!response || response == '' || response === NOT_AVAILABLE) {
// Empty or NA response
return false;
} else if (respJson && (respJson.RESULT === NOT_AVAILABLE || respJson.data == '' || respJson.data === NOT_AVAILABLE)) {
// Response type is json with value NA
return false;
} else { return true; }
}

/**
* Ignore not available response JSON
* @param obj
* Verify the response json is valid
* @param respJson - parsed json response
* @returns {boolean}
*/
function isValidResponse(obj) {
function isValidResponseJson(respJson) {
if (respJson && 'data' in respJson) {
return true;
} else { return false; }
}

/**
* Generate standard UUID string
* @return {string}
*/
function generateGUID() {
let d = new Date().getTime();
const guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
return guid;
}

/**
* Read Intent IQ data from cookie or local storage
* @param key
* @return {string}
*/
export function readData(key) {
try {
obj = JSON.parse(obj);
return obj && obj['RESULT'] != NOT_AVAILABLE;
if (storage.hasLocalStorage()) {
return storage.getDataFromLocalStorage(key);
}
if (storage.cookiesAreEnabled()) {
return storage.getCookie(key);
}
} catch (error) {
utils.logError(error);
}
}

/**
* Store Intent IQ data in either cookie or local storage
* expiration date: 365 days
* @param key
* @param {string} value IntentIQ ID value to sintentIqIdSystem_spec.jstore
*/
function storeData(key, value) {
try {
utils.logInfo(MODULE_NAME + ': storing data: key=' + key + ' value=' + value);

if (value) {
if (storage.hasLocalStorage()) {
storage.setDataInLocalStorage(key, value);
}
const expiresStr = (new Date(Date.now() + (PCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString();
if (storage.cookiesAreEnabled()) {
storage.setCookie(key, value, expiresStr, 'LAX');
}
}
} catch (error) {
utils.logError(error);
return true;
}
}

/**
* Parse json if possible, else return null
* @param data
* @param {object|null}
*/
function tryParse(data) {
try {
return JSON.parse(data);
} catch (err) {
utils.logError(err);
return null;
}
}

Expand All @@ -51,7 +130,7 @@ export const intentIqIdSubmodule = {
* @returns {{intentIqId: {string}}|undefined}
*/
decode(value) {
return isValidId(value) ? { 'intentIqId': value } : undefined;
return isValidResponse(value, undefined) ? { 'intentIqId': value } : undefined;
},
/**
* performs action to obtain id and return a value in the callback's response argument
Expand All @@ -66,22 +145,42 @@ export const intentIqIdSubmodule = {
return;
}

// Read Intent IQ 1st party id or generate it if none exists
let firstPartyData = tryParse(readData(FIRST_PARTY_KEY));
if (!firstPartyData || !firstPartyData.pcid) {
const firstPartyId = generateGUID();
firstPartyData = { 'pcid': firstPartyId };
storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData));
}

// use protocol relative urls for http or https
let url = `https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`;
url += configParams.pcid ? '&pcid=' + encodeURIComponent(configParams.pcid) : '';
url += configParams.pai ? '&pai=' + encodeURIComponent(configParams.pai) : '';
if (firstPartyData) {
url += firstPartyData.pcid ? '&iiqidtype=2&iiqpcid=' + encodeURIComponent(firstPartyData.pcid) : '';
url += firstPartyData.pid ? '&pid=' + encodeURIComponent(firstPartyData.pid) : '';
}

const resp = function (callback) {
const callbacks = {
success: response => {
if (isValidId(response)) {
callback(response);
let respJson = tryParse(response);
if (isValidResponse(response, respJson) && isValidResponseJson(respJson)) {
// Store pid field if found in response json
if (firstPartyData && 'pcid' in firstPartyData && 'pid' in respJson) {
firstPartyData = {
'pcid': firstPartyData.pcid,
'pid': respJson.pid }
storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData));
}
callback(respJson.data);
} else {
callback();
}
},
error: error => {
utils.logError(`${MODULE_NAME}: ID fetch encountered an error`, error);
utils.logError(MODULE_NAME + ': ID fetch encountered an error', error);
callback();
}
};
Expand Down
60 changes: 45 additions & 15 deletions test/spec/modules/intentIqIdSystem_spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from 'chai';
import {intentIqIdSubmodule} from 'modules/intentIqIdSystem.js';
import {intentIqIdSubmodule, readData, FIRST_PARTY_KEY} from 'modules/intentIqIdSystem.js';
import * as utils from 'src/utils.js';
import {server} from 'test/mocks/xhr.js';

Expand Down Expand Up @@ -46,7 +46,7 @@ describe('IntentIQ tests', function () {
let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback;
submoduleCallback(callBackSpy);
let request = server.requests[0];
expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1');
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,
Expand All @@ -55,9 +55,9 @@ describe('IntentIQ tests', function () {
expect(callBackSpy.calledOnce).to.be.true;
});

it('should ignore NA and invalid responses', function () {
let resp = JSON.stringify({'RESULT': 'NA'});
expect(intentIqIdSubmodule.decode(resp)).to.equal(undefined);
it('should ignore NA and invalid responses in decode', function () {
// let resp = JSON.stringify({'RESULT': 'NA'});
// expect(intentIqIdSubmodule.decode(resp)).to.equal(undefined);
expect(intentIqIdSubmodule.decode('NA')).to.equal(undefined);
expect(intentIqIdSubmodule.decode('')).to.equal(undefined);
expect(intentIqIdSubmodule.decode(undefined)).to.equal(undefined);
Expand All @@ -68,7 +68,7 @@ describe('IntentIQ tests', function () {
let submoduleCallback = intentIqIdSubmodule.getId(paiConfigParams).callback;
submoduleCallback(callBackSpy);
let request = server.requests[0];
expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pai=11');
expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pai=11&iiqidtype=2&iiqpcid=');
request.respond(
200,
responseHeader,
Expand All @@ -82,7 +82,7 @@ describe('IntentIQ tests', function () {
let submoduleCallback = intentIqIdSubmodule.getId(pcidConfigParams).callback;
submoduleCallback(callBackSpy);
let request = server.requests[0];
expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12');
expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12&iiqidtype=2&iiqpcid=');
request.respond(
200,
responseHeader,
Expand All @@ -96,7 +96,7 @@ describe('IntentIQ tests', function () {
let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).callback;
submoduleCallback(callBackSpy);
let request = server.requests[0];
expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12&pai=11');
expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12&pai=11&iiqidtype=2&iiqpcid=');
request.respond(
200,
responseHeader,
Expand All @@ -110,23 +110,22 @@ describe('IntentIQ tests', function () {
let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback;
submoduleCallback(callBackSpy);
let request = server.requests[0];
expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1');
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(
204,
responseHeader,
''
);
expect(callBackSpy.calledOnce).to.be.true;
expect(request.response).to.equal('');
expect(logErrorStub.calledOnce).to.not.be.true;
});

it('should log an error and continue to callback if ajax request errors', function () {
let callBackSpy = sinon.spy();
let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback;
submoduleCallback(callBackSpy);
let request = server.requests[0];
expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1');
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(
503,
responseHeader,
Expand All @@ -135,17 +134,48 @@ describe('IntentIQ tests', function () {
expect(callBackSpy.calledOnce).to.be.true;
});

it('should log an error and continue to callback if ajax request errors', function () {
it('should ignore {RESULT: NA} in get id', function () {
let callBackSpy = sinon.spy();
let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback;
submoduleCallback(callBackSpy);
let request = server.requests[0];
expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1');
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(
503,
200,
responseHeader,
'Unavailable'
JSON.stringify({RESULT: 'NA'})
);
expect(callBackSpy.calledOnce).to.be.true;
expect(callBackSpy.args[0][0]).to.be.undefined;
});

it('should ignore NA in get id', function () {
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,
'NA'
);
expect(callBackSpy.calledOnce).to.be.true;
expect(callBackSpy.args[0][0]).to.be.undefined;
});

it('should parse result from json response', function () {
let callBackSpy = sinon.spy();
let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).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&pcid=12&pai=11&iiqidtype=2&iiqpcid=');
request.respond(
200,
responseHeader,
JSON.stringify({pid: 'test_pid', data: 'test_personid'})
);
expect(callBackSpy.calledOnce).to.be.true;
expect(callBackSpy.args[0][0]).to.be.eq('test_personid');
});
});

0 comments on commit 1a76585

Please sign in to comment.