Skip to content

Commit

Permalink
UtiqMtpIdSystem: create the Utiq MTP ID submodule (prebid#11372)
Browse files Browse the repository at this point in the history
* Creating new user ID module utiqMtpIdSystem to provide option for publishers using Utiq to send the martechPass. This is complementary to the UtiqIDSystem module

* fixing documentation to reflect correct name utiqMtp instead of utiq-mtp

* fixing inline doc and creating unit test for utiqMtpIdSystem

* Adding extra documentation to notify publishers to contact Utiq in order to integrate the Utiq script on their site

---------

Co-authored-by: root <[email protected]>
Co-authored-by: jdelhommeau <[email protected]>
  • Loading branch information
3 people authored and f.caspar committed May 14, 2024
1 parent e1fa415 commit 3e3b63e
Show file tree
Hide file tree
Showing 4 changed files with 349 additions and 0 deletions.
1 change: 1 addition & 0 deletions modules/.submodules.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"teadsIdSystem",
"tncIdSystem",
"utiqSystem",
"utiqMtpIdSystem",
"uid2IdSystem",
"euidIdSystem",
"unifiedIdSystem",
Expand Down
138 changes: 138 additions & 0 deletions modules/utiqMtpIdSystem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/**
* This module adds Utiq MTP provided by Utiq SA/NV to the User ID module
* The {@link module:modules/userId} module is required
* @module modules/utiqMtpIdSystem
* @requires module:modules/userId
*/
import { logInfo } from '../src/utils.js';
import { submodule } from '../src/hook.js';
import { getStorageManager } from '../src/storageManager.js';
import { MODULE_TYPE_UID } from '../src/activities/modules.js';

const MODULE_NAME = 'utiqMtpId';
const LOG_PREFIX = 'Utiq MTP module';

export const storage = getStorageManager({
moduleType: MODULE_TYPE_UID,
moduleName: MODULE_NAME,
});

/**
* Get the "mtid" from html5 local storage to make it available to the UserId module.
* @param config
* @returns {{utiqMtp: (*|string)}}
*/
function getUtiqFromStorage() {
let utiqPass;
let utiqPassStorage = JSON.parse(
storage.getDataFromLocalStorage('utiqPass')
);
logInfo(
`${LOG_PREFIX}: Local storage utiqPass: ${JSON.stringify(
utiqPassStorage
)}`
);

if (
utiqPassStorage &&
utiqPassStorage.connectId &&
Array.isArray(utiqPassStorage.connectId.idGraph) &&
utiqPassStorage.connectId.idGraph.length > 0
) {
utiqPass = utiqPassStorage.connectId.idGraph[0];
}
logInfo(
`${LOG_PREFIX}: Graph of utiqPass: ${JSON.stringify(
utiqPass
)}`
);

return {
utiqMtp:
utiqPass && utiqPass.mtid
? utiqPass.mtid
: null,
};
}

/** @type {Submodule} */
export const utiqMtpIdSubmodule = {
/**
* Used to link submodule with config
* @type {string}
*/
name: MODULE_NAME,
/**
* Decodes the stored id value for passing to bid requests.
* @function
* @returns {{utiqMtp: string} | null}
*/
decode(bidId) {
logInfo(`${LOG_PREFIX}: Decoded ID value ${JSON.stringify(bidId)}`);
return bidId.utiqMtp ? bidId : null;
},
/**
* Get the id from helper function and initiate a new user sync.
* @param config
* @returns {{callback: result}|{id: {utiqMtp: string}}}
*/
getId: function (config) {
const data = getUtiqFromStorage();
if (data.utiqMtp) {
logInfo(`${LOG_PREFIX}: Local storage ID value ${JSON.stringify(data)}`);
return { id: { utiqMtp: data.utiqMtp } };
} else {
if (!config) {
config = {};
}
if (!config.params) {
config.params = {};
}
if (
typeof config.params.maxDelayTime === 'undefined' ||
config.params.maxDelayTime === null
) {
config.params.maxDelayTime = 1000;
}
// Current delay and delay step in milliseconds
let currentDelay = 0;
const delayStep = 50;
const result = (callback) => {
const data = getUtiqFromStorage();
if (!data.utiqMtp) {
if (currentDelay > config.params.maxDelayTime) {
logInfo(
`${LOG_PREFIX}: No utiq value set after ${config.params.maxDelayTime} max allowed delay time`
);
callback(null);
} else {
currentDelay += delayStep;
setTimeout(() => {
result(callback);
}, delayStep);
}
} else {
const dataToReturn = { utiqMtp: data.utiqMtp };
logInfo(
`${LOG_PREFIX}: Returning ID value data of ${JSON.stringify(
dataToReturn
)}`
);
callback(dataToReturn);
}
};
return { callback: result };
}
},
eids: {
'utiqMtp': {
source: 'utiq-mtp.com',
atype: 1,
getValue: function (data) {
return data;
},
},
}
};

submodule('userId', utiqMtpIdSubmodule);
22 changes: 22 additions & 0 deletions modules/utiqMtpIdSystem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## Utiq User ID Submodule

Utiq MTP ID Module.

### Utiq installation ###

In order to use utiq in your prebid setup, you must first integrate utiq solution on your website as per https://docs.utiq.com/
If you are interested in using Utiq on your website, please contact Utiq on https://utiq.com/contact/

### Prebid integration ###

First, make sure to add the utiq MTP submodule to your Prebid.js package with:

```
gulp build --modules=userId,adfBidAdapter,ixBidAdapter,prebidServerBidAdapter,utiqMtpIdSystem
```

## Parameter Descriptions

| Params under userSync.userIds[] | Type | Description | Example |
| ------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------- |
| name | String | The name of the module | `"utiqMtpId"` |
188 changes: 188 additions & 0 deletions test/spec/modules/utiqMtpIdSystem_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { expect } from 'chai';
import { utiqMtpIdSubmodule } from 'modules/utiqMtpIdSystem.js';
import { storage } from 'modules/utiqMtpIdSystem.js';

describe('utiqMtpIdSystem', () => {
const utiqPassKey = 'utiqPass';

const getStorageData = (idGraph) => {
if (!idGraph) {
idGraph = {id: 501, domain: ''};
}
return {
'connectId': {
'idGraph': [idGraph],
}
}
};

it('should have the correct module name declared', () => {
expect(utiqMtpIdSubmodule.name).to.equal('utiqMtpId');
});

describe('utiqMtpId getId()', () => {
afterEach(() => {
storage.removeDataFromLocalStorage(utiqPassKey);
});

it('it should return object with key callback', () => {
expect(utiqMtpIdSubmodule.getId()).to.have.property('callback');
});

it('should return object with key callback with value type - function', () => {
storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData()));
expect(utiqMtpIdSubmodule.getId()).to.have.property('callback');
expect(typeof utiqMtpIdSubmodule.getId().callback).to.be.equal('function');
});

it('tests if localstorage & JSON works properly ', () => {
const idGraph = {
'domain': 'domainValue',
'mtid': 'mtidValue',
};
storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph)));
expect(JSON.parse(storage.getDataFromLocalStorage(utiqPassKey))).to.have.property('connectId');
});

it('returns {id: {utiq: data.utiq}} if we have the right data stored in the localstorage ', () => {
const idGraph = {
'domain': 'test.domain',
'mtid': 'mtidValue',
};
storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph)));
const response = utiqMtpIdSubmodule.getId();
expect(response).to.have.property('id');
expect(response.id).to.have.property('utiqMtp');
expect(response.id.utiqMtp).to.be.equal('mtidValue');
});

it('returns {utiqMtp: data.utiqMtp} if we have the right data stored in the localstorage right after the callback is called', (done) => {
const idGraph = {
'domain': 'test.domain',
'mtid': 'mtidValue',
};
const response = utiqMtpIdSubmodule.getId();
expect(response).to.have.property('callback');
expect(response.callback.toString()).contain('result(callback)');

if (typeof response.callback === 'function') {
storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph)));
response.callback(function (result) {
expect(result).to.not.be.null;
expect(result).to.have.property('utiqMtp');
expect(result.utiqMtp).to.be.equal('mtidValue');
done()
})
}
});

it('returns {utiqMtp: data.utiqMtp} if we have the right data stored in the localstorage right after 500ms delay', (done) => {
const idGraph = {
'domain': 'test.domain',
'mtid': 'mtidValue',
};

const response = utiqMtpIdSubmodule.getId();
expect(response).to.have.property('callback');
expect(response.callback.toString()).contain('result(callback)');

if (typeof response.callback === 'function') {
setTimeout(() => {
storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph)));
}, 500);
response.callback(function (result) {
expect(result).to.not.be.null;
expect(result).to.have.property('utiqMtp');
expect(result.utiqMtp).to.be.equal('mtidValue');
done()
})
}
});

it('returns null if we have the data stored in the localstorage after 500ms delay and the max (waiting) delay is only 200ms ', (done) => {
const idGraph = {
'domain': 'test.domain',
'mtid': 'mtidValue',
};

const response = utiqMtpIdSubmodule.getId({params: {maxDelayTime: 200}});
expect(response).to.have.property('callback');
expect(response.callback.toString()).contain('result(callback)');

if (typeof response.callback === 'function') {
setTimeout(() => {
storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph)));
}, 500);
response.callback(function (result) {
expect(result).to.be.null;
done()
})
}
});
});

describe('utiq decode()', () => {
const VALID_API_RESPONSES = [
{
expected: '32a97f612',
payload: {
utiqMtp: '32a97f612'
}
},
{
expected: '32a97f61',
payload: {
utiqMtp: '32a97f61',
}
},
];
VALID_API_RESPONSES.forEach(responseData => {
it('should return a newly constructed object with the utiqMtp for a payload with {utiqMtp: value}', () => {
expect(utiqMtpIdSubmodule.decode(responseData.payload)).to.deep.equal(
{utiqMtp: responseData.expected}
);
});
});

[{}, '', {foo: 'bar'}].forEach((response) => {
it(`should return null for an invalid response "${JSON.stringify(response)}"`, () => {
expect(utiqMtpIdSubmodule.decode(response)).to.be.null;
});
});
});

describe('utiq messageHandler', () => {
afterEach(() => {
storage.removeDataFromLocalStorage(utiqPassKey);
});

const domains = [
'domain1',
'domain2',
'domain3',
];

domains.forEach(domain => {
it(`correctly sets utiq value for domain name ${domain}`, (done) => {
const idGraph = {
'domain': domain,
'mtid': 'mtidValue',
};

storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph)));

const eventData = {
data: `{\"msgType\":\"MNOSELECTOR\",\"body\":{\"url\":\"https://${domain}/some/path\"}}`
};

window.dispatchEvent(new MessageEvent('message', eventData));

const response = utiqMtpIdSubmodule.getId();
expect(response).to.have.property('id');
expect(response.id).to.have.property('utiqMtp');
expect(response.id.utiqMtp).to.be.equal('mtidValue');
done();
});
});
});
});

0 comments on commit 3e3b63e

Please sign in to comment.