Skip to content

Commit

Permalink
Liveintent liveconnect module (#4803)
Browse files Browse the repository at this point in the history
* Use LiveIntent's liveconnect.
Fire an event each time id is resolved.

* Rename variables. Simplify imports.

* Example page for LiveIntent Id module

* Add application id param

* Expose all possible configuration options for LiveConnect

* Bumping version of live-connect-js to support:
- storage manipulation provided by PBJS
- ajax calls directly from PBJS

* Removed spec whitespace.

* Removed spec whitespace.

* Removed spec for cookies

* Reshuffled variables.

* Re-added `findSimilarCookies` spec for further checks

* Added a comment on the function to find all cookies that match a certain name.

* Fixing setCookie not to include the expires as undefined.

* Fixing setCookie not to include the expires as undefined.

* Updated live-connect-js to 1.1.1, fixing the bug where the wrapper name was sent with an incorrect param name.
PR for this change : LiveIntent/live-connect#4

The effects can be seen in the specs, where the incorrect `wrpn` sent as a pixel is now `wpn`

* Removed whitespace.

Co-authored-by: janko <janko.ulaga@gmail.com>
  • Loading branch information
melgenek and jankoulaga committed Mar 18, 2020
1 parent 0de00fe commit 9fc9579
Show file tree
Hide file tree
Showing 8 changed files with 298 additions and 77 deletions.
3 changes: 2 additions & 1 deletion allowedModules.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ module.exports = {
...sharedWhiteList,
'criteo-direct-rsa-validate',
'jsencrypt',
'crypto-js'
'crypto-js',
'live-connect' // Maintained by LiveIntent : https://github.com/liveintent-berlin/live-connect/
],
'src': [
...sharedWhiteList,
Expand Down
142 changes: 102 additions & 40 deletions modules/liveIntentIdSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,95 @@
* @module modules/liveIntentIdSystem
* @requires module:modules/userId
*/
import * as utils from '../src/utils.js'
import {ajax} from '../src/ajax.js';
import {submodule} from '../src/hook.js';
import * as utils from '../src/utils.js';
import { ajax } from '../src/ajax.js';
import { submodule } from '../src/hook.js';
import { LiveConnect } from 'live-connect-js/cjs/live-connect.js';
import { uspDataHandler } from '../src/adapterManager.js';

const MODULE_NAME = 'liveIntentId';
const LIVE_CONNECT_DUID_KEY = '_li_duid';
const DOMAIN_USER_ID_QUERY_PARAM_KEY = 'duid';
const DEFAULT_LIVEINTENT_IDENTITY_URL = 'https://idx.liadm.com';
const DEFAULT_PREBID_SOURCE = 'prebid';

let eventFired = false;
let liveConnect = null;

/**
* This function is used in tests
*/
export function reset() {
eventFired = false;
liveConnect = null;
}

function parseLiveIntentCollectorConfig(collectConfig) {
const config = {};
if (collectConfig) {
if (collectConfig.appId) {
config.appId = collectConfig.appId;
}
if (collectConfig.fpiStorageStrategy) {
config.storageStrategy = collectConfig.fpiStorageStrategy;
}
if (collectConfig.fpiExpirationDays) {
config.expirationDays = collectConfig.fpiExpirationDays;
}
if (collectConfig.collectorUrl) {
config.collectorUrl = collectConfig.collectorUrl;
}
}
return config;
}

function initializeLiveConnect(configParams) {
if (liveConnect) {
return liveConnect;
}

const publisherId = configParams && configParams.publisherId;
if (!publisherId && typeof publisherId !== 'string') {
utils.logError(`${MODULE_NAME} - publisherId must be defined, not a '${publisherId}'`);
return;
}

const identityResolutionConfig = {
source: 'prebid',
publisherId: publisherId
};
if (configParams.url) {
identityResolutionConfig.url = configParams.url
}
if (configParams.partner) {
identityResolutionConfig.source = configParams.partner
}
if (configParams.storage && configParams.storage.expires) {
identityResolutionConfig.expirationDays = configParams.storage.expires;
}
if (configParams.ajaxTimeout) {
identityResolutionConfig.ajaxTimeout = configParams.ajaxTimeout;
}

const liveConnectConfig = parseLiveIntentCollectorConfig(configParams.liCollectConfig);
liveConnectConfig.wrapperName = 'prebid';
liveConnectConfig.identityResolutionConfig = identityResolutionConfig;
liveConnectConfig.identifiersToResolve = configParams.identifiersToResolve || [];
if (configParams.providedIdentifierName) {
liveConnectConfig.providedIdentifierName = configParams.providedIdentifierName;
}
const usPrivacyString = uspDataHandler.getConsentData();
if (usPrivacyString) {
liveConnectConfig.usPrivacyString = usPrivacyString;
}

// The second param is the storage object, which means that all LS & Cookie manipulation will go through PBJS utils.
liveConnect = LiveConnect(liveConnectConfig, utils);
return liveConnect;
}

function tryFireEvent() {
if (!eventFired && liveConnect) {
liveConnect.fire();
eventFired = true;
}
}

/** @type {Submodule} */
export const liveIntentIdSubmodule = {
Expand All @@ -28,14 +108,21 @@ export const liveIntentIdSubmodule = {
* `publisherId` params.
* @function
* @param {{unifiedId:string}} value
* @param {SubmoduleParams|undefined} [configParams]
* @returns {{lipb:Object}}
*/
decode(value) {
decode(value, configParams) {
function composeIdObject(value) {
const base = {'lipbid': value['unifiedId']};
const base = { 'lipbid': value['unifiedId'] };
delete value.unifiedId;
return {'lipb': {...base, ...value}};
return { 'lipb': { ...base, ...value } };
}

if (configParams) {
initializeLiveConnect(configParams);
tryFireEvent();
}

return (value && typeof value['unifiedId'] === 'string') ? composeIdObject(value) : undefined;
},

Expand All @@ -46,38 +133,13 @@ export const liveIntentIdSubmodule = {
* @returns {IdResponse|undefined}
*/
getId(configParams) {
const publisherId = configParams && configParams.publisherId;
if (!publisherId && typeof publisherId !== 'string') {
utils.logError(`${MODULE_NAME} - publisherId must be defined, not a '${publisherId}'`);
const liveConnect = initializeLiveConnect(configParams);
if (!liveConnect) {
return;
}
let baseUrl = DEFAULT_LIVEINTENT_IDENTITY_URL;
let source = DEFAULT_PREBID_SOURCE;
if (configParams.url) {
baseUrl = configParams.url
}
if (configParams.partner) {
source = configParams.partner
}

const additionalIdentifierNames = configParams.identifiersToResolve || [];

const additionalIdentifiers = additionalIdentifierNames.concat([LIVE_CONNECT_DUID_KEY]).reduce((obj, identifier) => {
const value = utils.getCookie(identifier) || utils.getDataFromLocalStorage(identifier);
const key = identifier.replace(LIVE_CONNECT_DUID_KEY, DOMAIN_USER_ID_QUERY_PARAM_KEY);
if (value) {
if (typeof value === 'object') {
obj[key] = JSON.stringify(value);
} else {
obj[key] = value;
}
}
return obj
}, {});

const queryString = utils.parseQueryStringParameters(additionalIdentifiers)
const url = `${baseUrl}/idex/${source}/${publisherId}?${queryString}`;

tryFireEvent();
// Don't do the internal ajax call, but use the composed url and fire it via PBJS ajax module
const url = liveConnect.resolutionCallUrl();
const result = function (callback) {
ajax(url, response => {
let responseObj = {};
Expand Down
16 changes: 14 additions & 2 deletions modules/userId/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
* @summary decode a stored value for passing to bid requests
* @name Submodule#decode
* @param {Object|string} value
* @param {SubmoduleParams|undefined} configParams
* @return {(Object|undefined)}
*/

Expand All @@ -63,6 +64,14 @@
* @property {(number|undefined)} refreshInSeconds - if not empty, this value defines the maximum time span in seconds before refreshing user ID stored in browser
*/

/**
* @typedef {Object} LiveIntentCollectConfig
* @property {(string|undefined)} fpiStorageStrategy - defines whether the first party identifiers that LiveConnect creates and updates are stored in a cookie jar, local storage, or not created at all
* @property {(number|undefined)} fpiExpirationDays - the expiration time of an identifier created and updated by LiveConnect
* @property {(string|undefined)} collectorUrl - defines where the LiveIntentId signal pixels are pointing to
* @property {(string|undefined)} appId - the unique identifier of the application in question
*/

/**
* @typedef {Object} SubmoduleParams
* @property {(string|undefined)} partner - partner url param value
Expand All @@ -72,7 +81,10 @@
* @property {(boolean|undefined)} extend - extend expiration time on each access. default is false.
* @property {(string|undefined)} pid - placement id url param value
* @property {(string|undefined)} publisherId - the unique identifier of the publisher in question
* @property {(string|undefined)} ajaxTimeout - the number of milliseconds a resolution request can take before automatically being terminated
* @property {(array|undefined)} identifiersToResolve - the identifiers from either ls|cookie to be attached to the getId query
* @property {(string|undefined)} providedIdentifierName - defines the name of an identifier that can be found in local storage or in the cookie jar that can be sent along with the getId request. This parameter should be used whenever a customer is able to provide the most stable identifier possible
* @property {(LiveIntentCollectConfig|undefined)} liCollectConfig - the config for LiveIntent's collect requests
*/

/**
Expand Down Expand Up @@ -417,7 +429,7 @@ function initSubmodules(submodules, consentData) {

if (storedId) {
// cache decoded value (this is copied to every adUnit bid)
submodule.idObj = submodule.submodule.decode(storedId);
submodule.idObj = submodule.submodule.decode(storedId, submodule.config);
}
} else if (submodule.config.value) {
// cache decoded value (this is copied to every adUnit bid)
Expand All @@ -426,7 +438,7 @@ function initSubmodules(submodules, consentData) {
const response = submodule.submodule.getId(submodule.config.params, consentData, undefined);
if (utils.isPlainObject(response)) {
if (typeof response.callback === 'function') { submodule.callback = response.callback; }
if (response.id) { submodule.idObj = submodule.submodule.decode(response.id); }
if (response.id) { submodule.idObj = submodule.submodule.decode(response.id, submodule.config); }
}
}
carry.push(submodule);
Expand Down
37 changes: 34 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
"express": "^4.15.4",
"fun-hooks": "^0.9.8",
"jsencrypt": "^3.0.0-rc.1",
"just-clone": "^1.0.2"
"just-clone": "^1.0.2",
"live-connect-js": "1.1.1"
}
}
27 changes: 26 additions & 1 deletion src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -882,10 +882,35 @@ export function getCookie(name) {
*/
export function setCookie(key, value, expires, sameSite, domain) {
if (hasDeviceAccess()) {
document.cookie = `${key}=${encodeURIComponent(value)}${(expires !== '') ? `; expires=${expires}` : ''}; path=/${sameSite ? `; SameSite=${sameSite}` : ''}${domain ? `; domain=${domain}` : ''}`;
const domainPortion = (domain && domain !== '') ? ` ;domain=${encodeURIComponent(domain)}` : '';
const expiresPortion = (expires && expires !== '') ? ` ;expires=${expires}` : '';
document.cookie = `${key}=${encodeURIComponent(value)}${expiresPortion}; path=/${domainPortion}${sameSite ? `; SameSite=${sameSite}` : ''}`;
}
}

/**
* Returns all cookie values from the jar whose names contain the `keyLike`
* Needs to exist in `utils.js` as it follows the StorageHandler interface defined in live-connect-js. If that module were to be removed, this function can go as well.
* @param {string} keyLike
* @return {[]}
*/
export function findSimilarCookies(keyLike) {
const all = [];
if (hasDeviceAccess()) {
const cookies = document.cookie.split(';');
while (cookies.length) {
const cookie = cookies.pop();
let separatorIndex = cookie.indexOf('=');
separatorIndex = separatorIndex < 0 ? cookie.length : separatorIndex;
const cookieName = decodeURIComponent(cookie.slice(0, separatorIndex).replace(/^\s+/, ''));
if (cookieName.indexOf(keyLike) >= 0) {
all.push(decodeURIComponent(cookie.slice(separatorIndex + 1)));
}
}
}
return all;
}

/**
* @returns {boolean}
*/
Expand Down
Loading

0 comments on commit 9fc9579

Please sign in to comment.