Skip to content

Commit

Permalink
ID Library
Browse files Browse the repository at this point in the history
  • Loading branch information
skocheri committed Oct 16, 2020
1 parent 4662f06 commit 7440f9e
Show file tree
Hide file tree
Showing 3 changed files with 319 additions and 0 deletions.
246 changes: 246 additions & 0 deletions modules/idLibrary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
import {getGlobal} from '../src/prebidGlobal.js';
import {ajax} from '../src/ajax.js';
import {config} from '../src/config.js';
import * as utils from '../src/utils.js';
import MD5 from 'crypto-js/md5.js';

let email;
const LOG_PRE_FIX = 'ID-Library: ';
const CONF_DEFAULT_OBSERVER_DEBOUNCE_MS = 250;
const OBSERVER_CONFIG = {
subtree: true,
attributes: true,
attributeOldValue: false,
childList: true,
attirbuteFilter: ['value'],
characterData: true,
characterDataOldValue: false
};
const logInfo = createLogInfo(LOG_PRE_FIX);
const logError = createLogError(LOG_PRE_FIX);

function createLogInfo(prefix) {
return function (...strings) {
utils.logInfo(prefix + ' ', ...strings);
}
}

function createLogError(prefix) {
return function (...strings) {
utils.logError(prefix + ' ', ...strings);
}
}

function getEmail(value) {
const matched = value.match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/gi);
if (!matched) {
return null;
}
return matched[0];
}

function hasEmail(value) {
const email = getEmail(value);
return !!email;
}

function bodyAction(conf, mutations, observer) {
logInfo('BODY observer on debounce called');

if (email) {
observer.disconnect();
logInfo('Email is found, body observer disconnected');
}

const body = document.body.innerHTML;

if (hasEmail(body)) {
email = getEmail(body);

logInfo(`Email obtained from the body ${email}`);
observer.disconnect();
logInfo('Post data on email found in body');
postData(conf.url);
}
}

function targetAction(conf, mutations, observer) {
logInfo('Target observer called');

mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
email = node.textContent;

if (email) {
logInfo('Email obtained from the target ' + email);
observer.disconnect();
logInfo(' Post data on email found in target');
postData(conf.url);
}
});
});
}

function addInputElementsElementListner(conf) {
logInfo('Adding input element listeners');
const inputs = document.querySelectorAll('input[type=text], input[type=email]');
inputs.forEach((input) => {
logInfo(` Original Value in Input = ${input.value}`);
input.addEventListener('change', event => processInputChange(event, conf));
input.addEventListener('blur', event => processInputChange(event, conf));
});
}

function removeInputElementsElementListner(conf) {
logInfo('Removing input element listeners');
const inputs = document.querySelectorAll('input[type=text], input[type=email]');
inputs.forEach((input) => {
input.removeEventListener('change', event => processInputChange(event, conf));
input.removeEventListener('blur', event => processInputChange(event, conf));
});
}

function processInputChange(event, conf) {
const value = event.target.value;
logInfo(`Modified Value of input ${event.target.value}`);
if (hasEmail(value)) {
email = getEmail(value);

logInfo('Email found in input ' + email);
postData(conf.url);
removeInputElementsElementListner(conf);
}
}

function debounce(func, wait, immediate) {
let timeout;

return function (...args) {
const context = this;
const later = function () {
timeout = null;
if (!immediate) {
func.apply(context, args);
}
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) {
func.apply(context, args);
}
};
}

function handleTargetElement(conf) {
const targetObserver = new MutationObserver(function (mutations, observer) {
logInfo('target observer called');
debounce(targetAction(conf, mutations, observer), conf.debounce, false);
});

const targetElement = document.getElementById(conf.target);
if (targetElement) {
email = targetElement.innerText;

if (!email) {
logInfo('Finding the email with observer');
targetObserver.observe(targetElement, OBSERVER_CONFIG);
} else {
logInfo(' Target found with target ' + email);
logInfo(' Post data on email found in target with target');
postData(conf.url);
}
}
}

function handleBodyElements(conf) {
if (doesInputElementsHaveEmail()) {
logInfo('Email found in input elements ' + email);
logInfo('Post data on email found in target without');
postData(conf.url);
return;
}
if (hasEmail(document.body.innerHTML)) {
email = getEmail(document.body.innerHTML);

logInfo('Email found in body ' + email);
logInfo(' Post data on email found in the body without observer');
postData(conf.url);
return;
}
addInputElementsElementListner(conf);
const bodyObserver = new MutationObserver(function (mutations, observer) {
logInfo('Body observer called');
debounce(bodyAction(conf, mutations, observer), conf.debounce, false);
});
bodyObserver.observe(document.body, OBSERVER_CONFIG);
}

function doesInputElementsHaveEmail() {
const inputs = document.getElementsByTagName('input');

for (let index = 0; index < inputs.length; ++index) {
const curInput = inputs[index];

if (hasEmail(curInput.value)) {
email = getEmail(curInput.value);
return true;
}
}
return false;
}

function syncCallback() {
return {
success: function (responseBody) {
logInfo(' Data synced successfully.');
},
error: function () {
logInfo(' Data sync failed.');
}
}
}

function postData(url) {
(getGlobal()).refreshUserIds();
const userIds = (getGlobal()).getUserIds();
if (!userIds || userIds.length === 0) {
return;
}
logInfo(' Users' + JSON.stringify(userIds));
const syncPayload = {};
syncPayload.hid = MD5(email).toString();
syncPayload.uids = JSON.stringify(userIds);
const payloadString = JSON.stringify(syncPayload);
logInfo(payloadString);
ajax(url, syncCallback(), payloadString, {method: 'POST', withCredentials: true});
}

function associateIds(conf) {
if (window.MutationObserver || window.WebKitMutationObserver) {
if (conf.target) {
handleTargetElement(conf);
} else {
handleBodyElements(conf);
}
}
}

export function setConfig(config) {
if (!config) {
logError('Required confirguration not provided');
return;
}
if (!config.url) {
logError('The required url is not configured');
return;
}
if (!config.debounce) {
config.debounce = CONF_DEFAULT_OBSERVER_DEBOUNCE_MS;
logInfo('Set default observer debounce to ' + CONF_DEFAULT_OBSERVER_DEBOUNCE_MS);
}

associateIds(config);
}

config.getConfig('idLibrary', config => setConfig(config.idLibrary));
22 changes: 22 additions & 0 deletions modules/idLibrary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## ID Library Configuration Example


|Param |Required |Description |
|----------------|-------------------------------|-----------------------------|
|url |Yes | The url endpoint is used to post the hashed email and user ids. |
|target |No |It should contain the element id from which the email can be read. |
|debounce |No | Time in milliseconds before the email and ids are fetched |

### Example
```
pbjs.setConfig({
idLibrary:{
url: <url>,
debounce: 250,
target: 'username'
},
});
```


```
51 changes: 51 additions & 0 deletions test/spec/modules/idLibrary_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import * as utils from 'src/utils.js';
import * as idlibrary from 'modules/idLibrary.js';

var expect = require('chai').expect;

describe('currency', function () {
let fakeCurrencyFileServer;
let sandbox;
let clock;

let fn = sinon.spy();

beforeEach(function () {
fakeCurrencyFileServer = sinon.fakeServer.create();
sinon.stub(utils, 'logInfo');
sinon.stub(utils, 'logError');
});

afterEach(function () {
utils.logInfo.restore();
utils.logError.restore();
fakeCurrencyFileServer.restore();
idlibrary.setConfig({});
});

describe('setConfig', function () {
beforeEach(function() {
sandbox = sinon.sandbox.create();
clock = sinon.useFakeTimers(1046952000000); // 2003-03-06T12:00:00Z
});

afterEach(function () {
sandbox.restore();
clock.restore();
});

it('results when no config available', function () {
idlibrary.setConfig({});
sinon.assert.called(utils.logError);
});
it('results with config available', function () {
idlibrary.setConfig({ 'url': 'URL' });
sinon.assert.called(utils.logInfo);
});
it('results with config default debounce ', function () {
let config = { 'url': 'URL' }
idlibrary.setConfig(config);
expect(config.debounce).to.be.equal(250);
});
});
});

0 comments on commit 7440f9e

Please sign in to comment.