You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@unomi.apache.org by jk...@apache.org on 2022/09/05 17:51:11 UTC
[unomi-tracker] branch UNOMI-610-new-tracker updated: UNOMI-610: base tracker first draft
This is an automated email from the ASF dual-hosted git repository.
jkevan pushed a commit to branch UNOMI-610-new-tracker
in repository https://gitbox.apache.org/repos/asf/unomi-tracker.git
The following commit(s) were added to refs/heads/UNOMI-610-new-tracker by this push:
new 6d3d603 UNOMI-610: base tracker first draft
6d3d603 is described below
commit 6d3d6032a08b1f6ef17b2c8133354d5cde9a799b
Author: Kevan <ke...@jahia.com>
AuthorDate: Mon Sep 5 19:50:59 2022 +0200
UNOMI-610: base tracker first draft
---
.gitignore | 1 -
dist/apache-unomi-tracker.cjs.js | 1241 ++++++++++++++++
dist/apache-unomi-tracker.esm.js | 1237 ++++++++++++++++
dist/apache-unomi-tracker.umd.js | 2887 ++++++++++++++++++++++++++++++++++++++
4 files changed, 5365 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index 0f54d80..ee8ecb9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -264,7 +264,6 @@ GitHub.sublime-settings
.stylelintcache
storybook-static/
-dist/
yalc.lock
.yalc
diff --git a/dist/apache-unomi-tracker.cjs.js b/dist/apache-unomi-tracker.cjs.js
new file mode 100644
index 0000000..6f6ed39
--- /dev/null
+++ b/dist/apache-unomi-tracker.cjs.js
@@ -0,0 +1,1241 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+var es6CrawlerDetect = require('es6-crawler-detect');
+
+function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new T [...]
+
+function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
+
+function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
+var newTracker = function newTracker() {
+ var wem = {
+ enableWem: function enableWem() {
+ wem._enableWem(true);
+ },
+ disableWem: function disableWem() {
+ wem._enableWem(false);
+ },
+
+ /**
+ * This function initialize the context in the page it is called internally and should not be called twice in the same page
+ *
+ * @param {object} digitalData config of the tracker
+ */
+ init: function init(digitalData) {
+ // added for external tracker
+ // store digitalData in tracker instance instead of window.
+ wem.digitalData = digitalData; // new conf:
+
+ wem.trackerProfileIdCookieName = wem.digitalData.wemInitConfig.trackerProfileIdCookieName ? wem.digitalData.wemInitConfig.trackerProfileIdCookieName : "wem-profile-id";
+ wem.trackerSessionIdCookieName = wem.digitalData.wemInitConfig.trackerSessionIdCookieName ? wem.digitalData.wemInitConfig.trackerSessionIdCookieName : "wem-session-id";
+ wem.activateWem = wem.digitalData.wemInitConfig.activateWem;
+ var _wem$digitalData$wemI = wem.digitalData.wemInitConfig,
+ contextServerUrl = _wem$digitalData$wemI.contextServerUrl,
+ isPreview = _wem$digitalData$wemI.isPreview,
+ timeoutInMilliseconds = _wem$digitalData$wemI.timeoutInMilliseconds,
+ contextServerCookieName = _wem$digitalData$wemI.contextServerCookieName;
+ wem.contextServerCookieName = contextServerCookieName;
+ wem.contextServerUrl = contextServerUrl;
+ wem.timeoutInMilliseconds = timeoutInMilliseconds;
+ wem.formNamesToWatch = [];
+ wem.eventsPrevented = [];
+ wem.sessionID = wem.getCookie(wem.trackerSessionIdCookieName);
+ wem.fallback = false;
+
+ if (wem.sessionID === null) {
+ console.warn('[WEM] sessionID is null !');
+ } else if (!wem.sessionID || wem.sessionID === '') {
+ console.warn('[WEM] empty sessionID, setting to null !');
+ wem.sessionID = null;
+ }
+
+ if (isPreview) {
+ // do not execute fallback for preview!
+ return;
+ }
+
+ var cookieDisabled = !navigator.cookieEnabled;
+ var noSessionID = !wem.sessionID || wem.sessionID === '';
+ var crawlerDetected = navigator.userAgent;
+
+ if (crawlerDetected) {
+ var browserDetector = new es6CrawlerDetect.Crawler();
+ crawlerDetected = browserDetector.isCrawler(navigator.userAgent);
+ }
+
+ if (cookieDisabled || noSessionID || crawlerDetected) {
+ document.addEventListener('DOMContentLoaded', function () {
+ wem._executeFallback('navigator cookie disabled: ' + cookieDisabled + ', no sessionID: ' + noSessionID + ', web crawler detected: ' + crawlerDetected);
+ });
+ return;
+ }
+
+ wem._registerCallback(function () {
+ if (wem.cxs.profileId) {
+ wem.setCookie(wem.trackerProfileIdCookieName, wem.cxs.profileId);
+ }
+
+ if (!wem.cxs.profileId) {
+ wem.removeCookie(wem.trackerProfileIdCookieName);
+ } // process tracked events
+
+
+ var videoNamesToWatch = [];
+ var clickToWatch = [];
+
+ if (wem.cxs.trackedConditions && wem.cxs.trackedConditions.length > 0) {
+ for (var i = 0; i < wem.cxs.trackedConditions.length; i++) {
+ switch (wem.cxs.trackedConditions[i].type) {
+ case 'formEventCondition':
+ if (wem.cxs.trackedConditions[i].parameterValues && wem.cxs.trackedConditions[i].parameterValues.formId) {
+ wem.formNamesToWatch.push(wem.cxs.trackedConditions[i].parameterValues.formId);
+ }
+
+ break;
+
+ case 'videoViewEventCondition':
+ if (wem.cxs.trackedConditions[i].parameterValues && wem.cxs.trackedConditions[i].parameterValues.videoId) {
+ videoNamesToWatch.push(wem.cxs.trackedConditions[i].parameterValues.videoId);
+ }
+
+ break;
+
+ case 'clickOnLinkEventCondition':
+ if (wem.cxs.trackedConditions[i].parameterValues && wem.cxs.trackedConditions[i].parameterValues.itemId) {
+ clickToWatch.push(wem.cxs.trackedConditions[i].parameterValues.itemId);
+ }
+
+ break;
+ }
+ }
+ }
+
+ var forms = document.querySelectorAll('form');
+
+ for (var formIndex = 0; formIndex < forms.length; formIndex++) {
+ var form = forms.item(formIndex);
+ var formName = form.getAttribute('name') ? form.getAttribute('name') : form.getAttribute('id'); // test attribute data-form-id to not add a listener on FF form
+
+ if (formName && wem.formNamesToWatch.indexOf(formName) > -1 && form.getAttribute('data-form-id') == null) {
+ // add submit listener on form that we need to watch only
+ console.info('[WEM] watching form ' + formName);
+ form.addEventListener('submit', wem._formSubmitEventListener, true);
+ }
+ }
+
+ for (var videoIndex = 0; videoIndex < videoNamesToWatch.length; videoIndex++) {
+ var videoName = videoNamesToWatch[videoIndex];
+ var video = document.getElementById(videoName) || document.getElementById(wem._resolveId(videoName));
+
+ if (video) {
+ video.addEventListener('play', wem.sendVideoEvent);
+ video.addEventListener('ended', wem.sendVideoEvent);
+ console.info('[WEM] watching video ' + videoName);
+ } else {
+ console.warn('[WEM] unable to watch video ' + videoName + ', video not found in the page');
+ }
+ }
+
+ for (var clickIndex = 0; clickIndex < clickToWatch.length; clickIndex++) {
+ var clickIdName = clickToWatch[clickIndex];
+ var click = document.getElementById(clickIdName) || document.getElementById(wem._resolveId(clickIdName)) ? document.getElementById(clickIdName) || document.getElementById(wem._resolveId(clickIdName)) : document.getElementsByName(clickIdName)[0];
+
+ if (click) {
+ click.addEventListener('click', wem.sendClickEvent);
+ console.info('[WEM] watching click ' + clickIdName);
+ } else {
+ console.warn('[WEM] unable to watch click ' + clickIdName + ', element not found in the page');
+ }
+ }
+ }); // Load the context once document is ready
+
+
+ document.addEventListener('DOMContentLoaded', function () {
+ wem.DOMLoaded = true; // complete already registered events
+
+ wem._checkUncompleteRegisteredEvents(); // Dispatch javascript events for the experience (perso/opti displayed from SSR, based on unomi events)
+
+
+ wem._dispatchJSExperienceDisplayedEvents(); // Some event may not need to be send to unomi, check for them and filter them out.
+
+
+ wem._filterUnomiEvents(); // Add referrer info into digitalData.page object.
+
+
+ wem._processReferrer(); // Build view event
+
+
+ var viewEvent = wem.buildEvent('view', wem.buildTargetPage(), wem.buildSource(wem.digitalData.site.siteInfo.siteID, 'site'));
+ viewEvent.flattenedProperties = {}; // Add URLParameters
+
+ if (location.search) {
+ viewEvent.flattenedProperties['URLParameters'] = wem.convertUrlParametersToObj(location.search);
+ } // Add interests
+
+
+ if (wem.digitalData.interests) {
+ viewEvent.flattenedProperties['interests'] = wem.digitalData.interests;
+ } // Register the page view event, it's unshift because it should be the first event, this is just for logical purpose. (page view comes before perso displayed event for example)
+
+
+ wem._registerEvent(viewEvent, true);
+
+ if (wem.activateWem) {
+ wem.loadContext();
+ } else {
+ wem._executeFallback('wem is not activated on current page');
+ }
+ });
+ },
+ convertUrlParametersToObj: function convertUrlParametersToObj(searchString) {
+ if (!searchString) {
+ return null;
+ }
+
+ return searchString.replace(/^\?/, '') // Only trim off a single leading interrobang.
+ .split('&').reduce(function (result, next) {
+ if (next === '') {
+ return result;
+ }
+
+ var pair = next.split('=');
+ var key = decodeURIComponent(pair[0]);
+ var value = typeof pair[1] !== 'undefined' && decodeURIComponent(pair[1]) || undefined;
+
+ if (Object.prototype.hasOwnProperty.call(result, key)) {
+ // Check to see if this property has been met before.
+ if (Array.isArray(result[key])) {
+ // Is it already an array?
+ result[key].push(value);
+ } else {
+ // Make it an array.
+ result[key] = [result[key], value];
+ }
+ } else {
+ // First time seen, just add it.
+ result[key] = value;
+ }
+
+ return result;
+ }, {});
+ },
+
+ /**
+ * This function will register a personalization
+ *
+ * @param {object} personalization
+ * @param {object} variants
+ * @param {boolean} [ajax] Deprecated: Ajax rendering is not supported anymore
+ * @param {function} [resultCallback]
+ */
+ registerPersonalizationObject: function registerPersonalizationObject(personalization, variants, ajax, resultCallback) {
+ var target = personalization.id;
+
+ wem._registerPersonalizationCallback(personalization, function (result) {
+ var successfulFilters = [];
+
+ for (var i = 0; i < result.length; i++) {
+ successfulFilters.push(variants[result[i]]);
+ }
+
+ var selectedFilter = null;
+
+ if (successfulFilters.length > 0) {
+ selectedFilter = successfulFilters[0];
+ var minPos = successfulFilters[0].position;
+
+ if (minPos >= 0) {
+ for (var j = 1; j < successfulFilters.length; j++) {
+ if (successfulFilters[j].position < minPos) {
+ selectedFilter = successfulFilters[j];
+ }
+ }
+ }
+ }
+
+ if (resultCallback) {
+ // execute callback
+ resultCallback(successfulFilters, selectedFilter);
+ } else {
+ if (selectedFilter) {
+ var targetFilters = document.getElementById(target).children;
+
+ for (var fIndex in targetFilters) {
+ var filter = targetFilters.item(fIndex);
+
+ if (filter) {
+ filter.style.display = filter.id === selectedFilter.content ? '' : 'none';
+ }
+ } // we now add control group information to event if the user is in the control group.
+
+
+ if (wem._isInControlGroup(target)) {
+ console.info('[WEM] Profile is in control group for target: ' + target + ', adding to personalization event...');
+ selectedFilter.event.target.properties.inControlGroup = true;
+
+ if (selectedFilter.event.target.properties.variants) {
+ selectedFilter.event.target.properties.variants.forEach(function (variant) {
+ return variant.inControlGroup = true;
+ });
+ }
+ } // send event to unomi
+
+
+ wem.collectEvent(wem._completeEvent(selectedFilter.event), function () {
+ console.info('[WEM] Personalization event successfully collected.');
+ }, function () {
+ console.error('[WEM] Could not send personalization event.');
+ }); //Trigger variant display event for personalization
+
+ wem._dispatchJSExperienceDisplayedEvent(selectedFilter.event);
+ } else {
+ var elements = document.getElementById(target).children;
+
+ for (var eIndex in elements) {
+ var el = elements.item(eIndex);
+ el.style.display = 'none';
+ }
+ }
+ }
+ });
+ },
+
+ /**
+ * This function will register an optimization test or A/B test
+ *
+ * @param {string} optimizationTestNodeId
+ * @param {string} goalId
+ * @param {string} containerId
+ * @param {object} variants
+ * @param {boolean} [ajax] Deprecated: Ajax rendering is not supported anymore
+ * @param {object} [variantsTraffic]
+ */
+ registerOptimizationTest: function registerOptimizationTest(optimizationTestNodeId, goalId, containerId, variants, ajax, variantsTraffic) {
+ // check persona panel forced variant
+ var selectedVariantId = wem.getUrlParameter('wemSelectedVariantId-' + optimizationTestNodeId); // check already resolved variant stored in local
+
+ if (selectedVariantId === null) {
+ if (wem.storageAvailable('sessionStorage')) {
+ selectedVariantId = sessionStorage.getItem(optimizationTestNodeId);
+ } else {
+ selectedVariantId = wem.getCookie('selectedVariantId');
+
+ if (selectedVariantId != null && selectedVariantId === '') {
+ selectedVariantId = null;
+ }
+ }
+ } // select random variant and call unomi
+
+
+ if (!(selectedVariantId && variants[selectedVariantId])) {
+ var keys = Object.keys(variants);
+
+ if (variantsTraffic) {
+ var rand = 100 * Math.random() << 0;
+
+ for (var nodeIdentifier in variantsTraffic) {
+ if ((rand -= variantsTraffic[nodeIdentifier]) < 0 && selectedVariantId == null) {
+ selectedVariantId = nodeIdentifier;
+ }
+ }
+ } else {
+ selectedVariantId = keys[keys.length * Math.random() << 0];
+ }
+
+ if (wem.storageAvailable('sessionStorage')) {
+ sessionStorage.setItem(optimizationTestNodeId, selectedVariantId);
+ } else {
+ wem.setCookie('selectedVariantId', selectedVariantId, 1);
+ } // spread event to unomi
+
+
+ wem._registerEvent(wem._completeEvent(variants[selectedVariantId].event));
+ } //Trigger variant display event for optimization
+ // (Wrapped in DOMContentLoaded because opti are resulted synchronously at page load, so we dispatch the JS even after page load, to be sure that listeners are ready)
+
+
+ window.addEventListener('DOMContentLoaded', function () {
+ wem._dispatchJSExperienceDisplayedEvent(variants[selectedVariantId].event);
+ });
+
+ if (selectedVariantId) {
+ // update persona panel selected variant
+ if (window.optimizedContentAreas && window.optimizedContentAreas[optimizationTestNodeId]) {
+ window.optimizedContentAreas[optimizationTestNodeId].selectedVariant = selectedVariantId;
+ } // display the good variant
+
+
+ document.getElementById(variants[selectedVariantId].content).style.display = '';
+ }
+ },
+
+ /**
+ * This function is used to load the current context in the page
+ *
+ * @param {boolean} [skipEvents=false] Should we send the events
+ * @param {boolean} [invalidate=false] Should we invalidate the current context
+ */
+ loadContext: function loadContext(skipEvents, invalidate) {
+ if (wem.contextLoaded) {
+ console.log('Context already requested by', wem.contextLoaded);
+ return;
+ }
+
+ var jsonData = {
+ requiredProfileProperties: wem.digitalData.wemInitConfig.requiredProfileProperties,
+ requiredSessionProperties: wem.digitalData.wemInitConfig.requiredSessionProperties,
+ requireSegments: wem.digitalData.wemInitConfig.requireSegments,
+ requireScores: wem.digitalData.wemInitConfig.requireScores,
+ source: wem.buildSourcePage()
+ };
+
+ if (!skipEvents) {
+ jsonData.events = wem.digitalData.events;
+ }
+
+ if (wem.digitalData.personalizationCallback) {
+ jsonData.personalizations = wem.digitalData.personalizationCallback.map(function (x) {
+ return x.personalization;
+ });
+ }
+
+ jsonData.sessionId = wem.sessionID;
+ var contextUrl = wem.contextServerUrl + '/context.json';
+
+ if (invalidate) {
+ contextUrl += '?invalidateSession=true&invalidateProfile=true';
+ }
+
+ wem.ajax({
+ url: contextUrl,
+ type: 'POST',
+ async: true,
+ contentType: 'text/plain;charset=UTF-8',
+ // Use text/plain to avoid CORS preflight
+ jsonData: jsonData,
+ dataType: 'application/json',
+ invalidate: invalidate,
+ success: wem._onSuccess,
+ error: function error() {
+ wem._executeFallback('error during context loading');
+ }
+ });
+ wem.contextLoaded = Error().stack;
+ console.info('[WEM] context loading...');
+ },
+
+ /**
+ * This function will send an event to Apache Unomi
+ * @param {object} event The event object to send, you can build it using wem.buildEvent(eventType, target, source)
+ * @param {function} successCallback will be executed in case of success
+ * @param {function} errorCallback will be executed in case of error
+ */
+ collectEvent: function collectEvent(event, successCallback, errorCallback) {
+ wem.collectEvents({
+ events: [event]
+ }, successCallback, errorCallback);
+ },
+
+ /**
+ * This function will send the events to Apache Unomi
+ *
+ * @param {object} events Javascript object { events: [event1, event2] }
+ * @param {function} successCallback will be executed in case of success
+ * @param {function} errorCallback will be executed in case of error
+ */
+ collectEvents: function collectEvents(events, successCallback, errorCallback) {
+ if (wem.fallback) {
+ // in case of fallback we dont want to collect any events
+ return;
+ }
+
+ events.sessionId = wem.sessionID ? wem.sessionID : '';
+ var data = JSON.stringify(events);
+ wem.ajax({
+ url: wem.contextServerUrl + '/eventcollector',
+ type: 'POST',
+ async: true,
+ contentType: 'text/plain;charset=UTF-8',
+ // Use text/plain to avoid CORS preflight
+ data: data,
+ dataType: 'application/json',
+ success: successCallback,
+ error: errorCallback
+ });
+ },
+
+ /**
+ * This function will build an event of type click and send it to Apache Unomi
+ *
+ * @param {object} event javascript
+ * @param {function} [successCallback] will be executed if case of success
+ * @param {function} [errorCallback] will be executed if case of error
+ */
+ sendClickEvent: function sendClickEvent(event, successCallback, errorCallback) {
+ if (event.target.id || event.target.name) {
+ console.info('[WEM] Send click event');
+ var targetId = event.target.id ? event.target.id : event.target.name;
+ var clickEvent = wem.buildEvent('click', wem.buildTarget(targetId, event.target.localName), wem.buildSourcePage());
+ var eventIndex = wem.eventsPrevented.indexOf(targetId);
+
+ if (eventIndex !== -1) {
+ wem.eventsPrevented.splice(eventIndex, 0);
+ } else {
+ wem.eventsPrevented.push(targetId);
+ event.preventDefault();
+ var target = event.target;
+ wem.collectEvent(clickEvent, function (xhr) {
+ console.info('[WEM] Click event successfully collected.');
+
+ if (successCallback) {
+ successCallback(xhr);
+ } else {
+ target.click();
+ }
+ }, function (xhr) {
+ console.error('[WEM] Could not send click event.');
+
+ if (errorCallback) {
+ errorCallback(xhr);
+ } else {
+ target.click();
+ }
+ });
+ }
+ }
+ },
+
+ /**
+ * This function will build an event of type video and send it to Apache Unomi
+ *
+ * @param {object} event javascript
+ * @param {function} [successCallback] will be executed if case of success
+ * @param {function} [errorCallback] will be executed if case of error
+ */
+ sendVideoEvent: function sendVideoEvent(event, successCallback, errorCallback) {
+ console.info('[WEM] catching video event');
+ var videoEvent = wem.buildEvent('video', wem.buildTarget(event.target.id, 'video', {
+ action: event.type
+ }), wem.buildSourcePage());
+ wem.collectEvent(videoEvent, function (xhr) {
+ console.info('[WEM] Video event successfully collected.');
+
+ if (successCallback) {
+ successCallback(xhr);
+ }
+ }, function (xhr) {
+ console.error('[WEM] Could not send video event.');
+
+ if (errorCallback) {
+ errorCallback(xhr);
+ }
+ });
+ },
+
+ /**
+ * This function return the basic structure for an event, it must be adapted to your need
+ *
+ * @param {string} eventType The name of your event
+ * @param {object} [target] The target object for your event can be build with wem.buildTarget(targetId, targetType, targetProperties)
+ * @param {object} [source] The source object for your event can be build with wem.buildSource(sourceId, sourceType, sourceProperties)
+ * @returns {{eventType: *, scope}}
+ */
+ buildEvent: function buildEvent(eventType, target, source) {
+ var event = {
+ eventType: eventType,
+ scope: wem.digitalData.scope
+ };
+
+ if (target) {
+ event.target = target;
+ }
+
+ if (source) {
+ event.source = source;
+ }
+
+ return event;
+ },
+
+ /**
+ * This function return an event of type form
+ *
+ * @param {string} formName The HTML name of id of the form to use in the target of the event
+ * @returns {*|{eventType: *, scope, source: {scope, itemId: string, itemType: string, properties: {}}, target: {scope, itemId: string, itemType: string, properties: {}}}}
+ */
+ buildFormEvent: function buildFormEvent(formName) {
+ return wem.buildEvent('form', wem.buildTarget(formName, 'form'), wem.buildSourcePage());
+ },
+
+ /**
+ * This function return the source object for a source of type page
+ *
+ * @returns {*|{scope, itemId: *, itemType: *}}
+ */
+ buildTargetPage: function buildTargetPage() {
+ return wem.buildTarget(wem.digitalData.page.pageInfo.pageID, 'page', wem.digitalData.page);
+ },
+
+ /**
+ * This function return the source object for a source of type page
+ *
+ * @returns {*|{scope, itemId: *, itemType: *}}
+ */
+ buildSourcePage: function buildSourcePage() {
+ return wem.buildSource(wem.digitalData.page.pageInfo.pageID, 'page', wem.digitalData.page);
+ },
+
+ /**
+ * This function return the basic structure for the target of your event
+ *
+ * @param {string} targetId The ID of the target
+ * @param {string} targetType The type of the target
+ * @param {object} [targetProperties] The optional properties of the target
+ * @returns {{scope, itemId: *, itemType: *}}
+ */
+ buildTarget: function buildTarget(targetId, targetType, targetProperties) {
+ return wem._buildObject(targetId, targetType, targetProperties);
+ },
+
+ /**
+ * This function return the basic structure for the source of your event
+ *
+ * @param {string} sourceId The ID of the source
+ * @param {string} sourceType The type of the source
+ * @param {object} [sourceProperties] The optional properties of the source
+ * @returns {{scope, itemId: *, itemType: *}}
+ */
+ buildSource: function buildSource(sourceId, sourceType, sourceProperties) {
+ return wem._buildObject(sourceId, sourceType, sourceProperties);
+ },
+
+ /*************************************/
+
+ /* Utility functions under this line */
+
+ /*************************************/
+
+ /**
+ * This is an utility function to set a cookie
+ *
+ * @param {string} cookieName name of the cookie
+ * @param {string} cookieValue value of the cookie
+ * @param {number} [expireDays] number of days to set the expire date
+ */
+ setCookie: function setCookie(cookieName, cookieValue, expireDays) {
+ var expires = '';
+
+ if (expireDays) {
+ var d = new Date();
+ d.setTime(d.getTime() + expireDays * 24 * 60 * 60 * 1000);
+ expires = '; expires=' + d.toUTCString();
+ }
+
+ document.cookie = cookieName + '=' + cookieValue + expires + '; path=/; SameSite=Strict';
+ },
+
+ /**
+ * This is an utility function to get a cookie
+ *
+ * @param {string} cookieName name of the cookie to get
+ * @returns {*} the value of the first cookie with the corresponding name or null if not found
+ */
+ getCookie: function getCookie(cookieName) {
+ var name = cookieName + '=';
+ var ca = document.cookie.split(';');
+
+ for (var i = 0; i < ca.length; i++) {
+ var c = ca[i];
+
+ while (c.charAt(0) == ' ') {
+ c = c.substring(1);
+ }
+
+ if (c.indexOf(name) == 0) {
+ return c.substring(name.length, c.length);
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * This is an utility function to remove a cookie
+ *
+ * @param {string} cookieName the name of the cookie to rename
+ */
+ removeCookie: function removeCookie(cookieName) {
+
+ wem.setCookie(cookieName, '', -1);
+ },
+
+ /**
+ * This is an utility function to execute AJAX call
+ *
+ * @param {object} options
+ */
+ ajax: function ajax(options) {
+ var xhr = new XMLHttpRequest();
+
+ if ('withCredentials' in xhr) {
+ xhr.open(options.type, options.url, options.async);
+ xhr.withCredentials = true;
+ } else if (typeof XDomainRequest != 'undefined') {
+ /* global XDomainRequest */
+ xhr = new XDomainRequest();
+ xhr.open(options.type, options.url);
+ }
+
+ if (options.contentType) {
+ xhr.setRequestHeader('Content-Type', options.contentType);
+ }
+
+ if (options.dataType) {
+ xhr.setRequestHeader('Accept', options.dataType);
+ }
+
+ if (options.responseType) {
+ xhr.responseType = options.responseType;
+ }
+
+ var requestExecuted = false;
+
+ if (wem.timeoutInMilliseconds !== -1) {
+ setTimeout(function () {
+ if (!requestExecuted) {
+ console.error('[WEM] XML request timeout, url: ' + options.url);
+ requestExecuted = true;
+
+ if (options.error) {
+ options.error(xhr);
+ }
+ }
+ }, wem.timeoutInMilliseconds);
+ }
+
+ xhr.onreadystatechange = function () {
+ if (!requestExecuted) {
+ if (xhr.readyState === 4) {
+ if (xhr.status === 200 || xhr.status === 204 || xhr.status === 304) {
+ if (xhr.responseText != null) {
+ requestExecuted = true;
+
+ if (options.success) {
+ options.success(xhr);
+ }
+ }
+ } else {
+ requestExecuted = true;
+
+ if (options.error) {
+ options.error(xhr);
+ }
+
+ console.error('[WEM] XML request error: ' + xhr.statusText + ' (' + xhr.status + ')');
+ }
+ }
+ }
+ };
+
+ if (options.jsonData) {
+ xhr.send(JSON.stringify(options.jsonData));
+ } else if (options.data) {
+ xhr.send(options.data);
+ } else {
+ xhr.send();
+ }
+ },
+
+ /**
+ * This is an utility function to check if the local storage is available or not
+ * @param type
+ * @returns {boolean}
+ */
+ storageAvailable: function storageAvailable(type) {
+ try {
+ var storage = window[type],
+ x = '__storage_test__';
+ storage.setItem(x, x);
+ storage.removeItem(x);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ },
+ dispatchJSEvent: function dispatchJSEvent(name, canBubble, cancelable, detail) {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent(name, canBubble, cancelable, detail);
+ document.dispatchEvent(event);
+ },
+
+ /**
+ * This is an utility function to get current url parameter value
+ * @param name, the name of the parameter
+ * @returns {string}
+ */
+ getUrlParameter: function getUrlParameter(name) {
+ name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
+ var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
+ var results = regex.exec(window.location.search);
+ return results === null ? null : decodeURIComponent(results[1].replace(/\+/g, ' '));
+ },
+
+ /*************************************/
+
+ /* Private functions under this line */
+
+ /*************************************/
+ _checkUncompleteRegisteredEvents: function _checkUncompleteRegisteredEvents() {
+ if (wem.digitalData && wem.digitalData.events) {
+ var _iterator = _createForOfIteratorHelper(wem.digitalData.events),
+ _step;
+
+ try {
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
+ var event = _step.value;
+
+ wem._completeEvent(event);
+ }
+ } catch (err) {
+ _iterator.e(err);
+ } finally {
+ _iterator.f();
+ }
+ }
+ },
+ _dispatchJSExperienceDisplayedEvents: function _dispatchJSExperienceDisplayedEvents() {
+ if (wem.digitalData && wem.digitalData.events) {
+ var _iterator2 = _createForOfIteratorHelper(wem.digitalData.events),
+ _step2;
+
+ try {
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
+ var event = _step2.value;
+
+ if (event.eventType === 'optimizationTestEvent' || event.eventType === 'personalizationEvent') {
+ wem._dispatchJSExperienceDisplayedEvent(event);
+ }
+ }
+ } catch (err) {
+ _iterator2.e(err);
+ } finally {
+ _iterator2.f();
+ }
+ }
+ },
+ _dispatchJSExperienceDisplayedEvent: function _dispatchJSExperienceDisplayedEvent(experienceUnomiEvent) {
+ if (!wem.fallback && experienceUnomiEvent && experienceUnomiEvent.target && experienceUnomiEvent.target.properties && experienceUnomiEvent.target.properties.variants && experienceUnomiEvent.target.properties.variants.length > 0) {
+ var typeMapper = {
+ optimizationTestEvent: 'optimization',
+ personalizationEvent: 'personalization'
+ };
+
+ var _iterator3 = _createForOfIteratorHelper(experienceUnomiEvent.target.properties.variants),
+ _step3;
+
+ try {
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
+ var variant = _step3.value;
+ var jsEventDetail = {
+ id: variant.id,
+ name: variant.systemName,
+ displayableName: variant.displayableName,
+ path: variant.path,
+ type: typeMapper[experienceUnomiEvent.eventType],
+ variantType: experienceUnomiEvent.target.properties.type,
+ tags: variant.tags,
+ nodeType: variant.nodeType,
+ wrapper: {
+ id: experienceUnomiEvent.target.itemId,
+ name: experienceUnomiEvent.target.properties.systemName,
+ displayableName: experienceUnomiEvent.target.properties.displayableName,
+ path: experienceUnomiEvent.target.properties.path,
+ tags: experienceUnomiEvent.target.properties.tags,
+ nodeType: experienceUnomiEvent.target.properties.nodeType
+ }
+ };
+ wem.dispatchJSEvent('displayWemVariant', false, false, jsEventDetail);
+ }
+ } catch (err) {
+ _iterator3.e(err);
+ } finally {
+ _iterator3.f();
+ }
+ }
+ },
+ _filterUnomiEvents: function _filterUnomiEvents() {
+ if (wem.digitalData && wem.digitalData.events) {
+ wem.digitalData.events = wem.digitalData.events.filter(function (event) {
+ return !event.properties || !event.properties.doNotSendToUnomi;
+ }).map(function (event) {
+ if (event.properties) {
+ delete event.properties.doNotSendToUnomi;
+ }
+
+ return event;
+ });
+ }
+ },
+ _completeEvent: function _completeEvent(event) {
+ if (!event.source) {
+ event.source = wem.buildSourcePage();
+ }
+
+ if (!event.scope) {
+ event.scope = wem.digitalData.scope;
+ }
+
+ if (event.target && !event.target.scope) {
+ event.target.scope = wem.digitalData.scope;
+ }
+
+ return event;
+ },
+ _registerEvent: function _registerEvent(event, unshift) {
+ if (wem.digitalData) {
+ if (wem.cxs) {
+ console.error('[WEM] already loaded, too late...');
+ return;
+ }
+ } else {
+ wem.digitalData = {};
+ }
+
+ wem.digitalData.events = wem.digitalData.events || [];
+
+ if (unshift) {
+ wem.digitalData.events.unshift(event);
+ } else {
+ wem.digitalData.events.push(event);
+ }
+ },
+ _registerCallback: function _registerCallback(onLoadCallback) {
+ if (wem.digitalData) {
+ if (wem.cxs) {
+ console.info('[WEM] digitalData object loaded, calling on load callback immediately and registering update callback...');
+
+ if (onLoadCallback) {
+ onLoadCallback(wem.digitalData);
+ }
+ } else {
+ console.info('[WEM] digitalData object present but not loaded, registering load callback...');
+
+ if (onLoadCallback) {
+ wem.digitalData.loadCallbacks = wem.digitalData.loadCallbacks || [];
+ wem.digitalData.loadCallbacks.push(onLoadCallback);
+ }
+ }
+ } else {
+ console.info('[WEM] No digital data object found, creating and registering update callback...');
+ wem.digitalData = {};
+
+ if (onLoadCallback) {
+ wem.digitalData.loadCallbacks = [];
+ wem.digitalData.loadCallbacks.push(onLoadCallback);
+ }
+ }
+ },
+ _registerPersonalizationCallback: function _registerPersonalizationCallback(personalization, callback) {
+ if (wem.digitalData) {
+ if (wem.cxs) {
+ console.error('[WEM] already loaded, too late...');
+ } else {
+ console.info('[WEM] digitalData object present but not loaded, registering sort callback...');
+ wem.digitalData.personalizationCallback = wem.digitalData.personalizationCallback || [];
+ wem.digitalData.personalizationCallback.push({
+ personalization: personalization,
+ callback: callback
+ });
+ }
+ } else {
+ wem.digitalData = {};
+ wem.digitalData.personalizationCallback = wem.digitalData.personalizationCallback || [];
+ wem.digitalData.personalizationCallback.push({
+ personalization: personalization,
+ callback: callback
+ });
+ }
+ },
+ _buildObject: function _buildObject(itemId, itemType, properties) {
+ var object = {
+ scope: wem.digitalData.scope,
+ itemId: itemId,
+ itemType: itemType
+ };
+
+ if (properties) {
+ object.properties = properties;
+ }
+
+ return object;
+ },
+ _onSuccess: function _onSuccess(xhr) {
+ wem.cxs = JSON.parse(xhr.responseText);
+
+ if (wem.digitalData.loadCallbacks && wem.digitalData.loadCallbacks.length > 0) {
+ console.info('[WEM] Found context server load callbacks, calling now...');
+
+ if (wem.digitalData.loadCallbacks) {
+ for (var i = 0; i < wem.digitalData.loadCallbacks.length; i++) {
+ wem.digitalData.loadCallbacks[i](wem.digitalData);
+ }
+ }
+
+ if (wem.digitalData.personalizationCallback) {
+ for (var j = 0; j < wem.digitalData.personalizationCallback.length; j++) {
+ wem.digitalData.personalizationCallback[j].callback(wem.cxs.personalizations[wem.digitalData.personalizationCallback[j].personalization.id]);
+ }
+ }
+ } // Put a marker to be able to know when wem is full loaded, context is loaded, and callbacks have been executed.
+
+
+ window.wemLoaded = true;
+ },
+ _executeFallback: function _executeFallback(logMessage) {
+ console.warn('[WEM] execute fallback' + (logMessage ? ': ' + logMessage : ''));
+ wem.fallback = true;
+ wem.cxs = {};
+
+ for (var index in wem.digitalData.loadCallbacks) {
+ wem.digitalData.loadCallbacks[index]();
+ }
+
+ if (wem.digitalData.personalizationCallback) {
+ for (var i = 0; i < wem.digitalData.personalizationCallback.length; i++) {
+ wem.digitalData.personalizationCallback[i].callback([wem.digitalData.personalizationCallback[i].personalization.strategyOptions.fallback]);
+ }
+ }
+ },
+ _processReferrer: function _processReferrer() {
+ var referrerURL = wem.digitalData.page.pageInfo.referringURL || document.referrer;
+ var sameDomainReferrer = false;
+
+ if (referrerURL) {
+ // parse referrer URL
+ var referrer = new URL(referrerURL); // Set sameDomainReferrer property
+
+ sameDomainReferrer = referrer.host === window.location.host; // only process referrer if it's not coming from the same site as the current page
+
+ if (!sameDomainReferrer) {
+ // get search element if it exists and extract search query if available
+ var search = referrer.search;
+ var query = undefined;
+
+ if (search && search != '') {
+ // parse parameters
+ var queryParams = [],
+ param;
+ var queryParamPairs = search.slice(1).split('&');
+
+ for (var i = 0; i < queryParamPairs.length; i++) {
+ param = queryParamPairs[i].split('=');
+ queryParams.push(param[0]);
+ queryParams[param[0]] = param[1];
+ } // try to extract query: q is Google-like (most search engines), p is Yahoo
+
+
+ query = queryParams.q || queryParams.p;
+ query = decodeURIComponent(query).replace(/\+/g, ' ');
+ } // register referrer event
+ // Create deep copy of wem.digitalData.page and add data to pageInfo sub object
+
+
+ if (wem.digitalData && wem.digitalData.page && wem.digitalData.page.pageInfo) {
+ wem.digitalData.page.pageInfo.referrerHost = referrer.host;
+ wem.digitalData.page.pageInfo.referrerQuery = query;
+ }
+ }
+ }
+
+ wem.digitalData.page.pageInfo.sameDomainReferrer = sameDomainReferrer;
+ },
+ _formSubmitEventListener: function _formSubmitEventListener(event) {
+ console.info('[WEM] Registering form event callback');
+ var form = event.target;
+ var formName = form.getAttribute('name') ? form.getAttribute('name') : form.getAttribute('id');
+
+ if (formName && wem.formNamesToWatch.indexOf(formName) > -1) {
+ console.info('[WEM] catching form ' + formName);
+ var eventCopy = document.createEvent('Event'); // Define that the event name is 'build'.
+
+ eventCopy.initEvent('submit', event.bubbles, event.cancelable);
+ event.stopImmediatePropagation();
+ event.preventDefault();
+ var formEvent = wem.buildFormEvent(formName); // merge form properties with event properties
+
+ formEvent.flattenedProperties = {
+ fields: wem._extractFormData(form)
+ };
+ wem.collectEvent(formEvent, function () {
+ form.removeEventListener('submit', wem._formSubmitEventListener, true);
+ form.dispatchEvent(eventCopy);
+
+ if (!eventCopy.defaultPrevented && !eventCopy.cancelBubble) {
+ form.submit();
+ }
+
+ form.addEventListener('submit', wem._formSubmitEventListener, true);
+ }, function (xhr) {
+ console.error('[WEM] Error while collecting form event: ' + xhr.status + ' ' + xhr.statusText);
+ xhr.abort();
+ form.removeEventListener('submit', wem._formSubmitEventListener, true);
+ form.dispatchEvent(eventCopy);
+
+ if (!eventCopy.defaultPrevented && !eventCopy.cancelBubble) {
+ form.submit();
+ }
+
+ form.addEventListener('submit', wem._formSubmitEventListener, true);
+ });
+ }
+ },
+ _extractFormData: function _extractFormData(form) {
+ var params = {};
+
+ for (var i = 0; i < form.elements.length; i++) {
+ var e = form.elements[i]; // ignore empty and undefined key (e.name)
+
+ if (e.name) {
+ switch (e.nodeName) {
+ case 'TEXTAREA':
+ case 'INPUT':
+ switch (e.type) {
+ case 'checkbox':
+ var checkboxes = document.querySelectorAll('input[name="' + e.name + '"]');
+
+ if (checkboxes.length > 1) {
+ if (!params[e.name]) {
+ params[e.name] = [];
+ }
+
+ if (e.checked) {
+ params[e.name].push(e.value);
+ }
+ }
+
+ break;
+
+ case 'radio':
+ if (e.checked) {
+ params[e.name] = e.value;
+ }
+
+ break;
+
+ default:
+ if (!e.value || e.value == '') {
+ // ignore element if no value is provided
+ break;
+ }
+
+ params[e.name] = e.value;
+ }
+
+ break;
+
+ case 'SELECT':
+ if (e.options && e.options[e.selectedIndex]) {
+ if (e.multiple) {
+ params[e.name] = [];
+
+ for (var j = 0; j < e.options.length; j++) {
+ if (e.options[j].selected) {
+ params[e.name].push(e.options[j].value);
+ }
+ }
+ } else {
+ params[e.name] = e.options[e.selectedIndex].value;
+ }
+ }
+
+ break;
+ }
+ }
+ }
+
+ return params;
+ },
+ _resolveId: function _resolveId(id) {
+ if (wem.digitalData.sourceLocalIdentifierMap) {
+ var source = Object.keys(wem.digitalData.sourceLocalIdentifierMap).filter(function (source) {
+ return id.indexOf(source) > 0;
+ });
+ return source ? id.replace(source, wem.digitalData.sourceLocalIdentifierMap[source]) : id;
+ }
+
+ return id;
+ },
+ _enableWem: function _enableWem(enable, callback) {
+ // display fallback if wem is not enable
+ wem.fallback = !enable; // remove cookies, reset cxs
+
+ if (!enable) {
+ wem.cxs = {};
+ document.cookie = wem.trackerProfileIdCookieName + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
+ document.cookie = wem.contextServerCookieName + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
+ delete wem.contextLoaded;
+ } else {
+ if (wem.DOMLoaded) {
+ wem.loadContext();
+ } else {
+ // As Dom loaded listener not triggered, enable global value.
+ wem.activateWem = true;
+ }
+ }
+
+ if (callback) {
+ callback(enable);
+ }
+
+ console.log("Wem ".concat(enable ? 'enabled' : 'disabled'));
+ },
+ _isInControlGroup: function _isInControlGroup(id) {
+ if (wem.cxs.profileProperties && wem.cxs.profileProperties.unomiControlGroups) {
+ var controlGroup = wem.cxs.profileProperties.unomiControlGroups.find(function (controlGroup) {
+ return controlGroup.id === id;
+ });
+
+ if (controlGroup) {
+ return true;
+ }
+ }
+
+ if (wem.cxs.sessionProperties && wem.cxs.sessionProperties.unomiControlGroups) {
+ var _controlGroup = wem.cxs.sessionProperties.unomiControlGroups.find(function (controlGroup) {
+ return controlGroup.id === id;
+ });
+
+ if (_controlGroup) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ };
+ return wem;
+};
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var useTracker = function useTracker() {
+ return newTracker();
+};
+
+exports.useTracker = useTracker;
diff --git a/dist/apache-unomi-tracker.esm.js b/dist/apache-unomi-tracker.esm.js
new file mode 100644
index 0000000..e0c8bd7
--- /dev/null
+++ b/dist/apache-unomi-tracker.esm.js
@@ -0,0 +1,1237 @@
+import { Crawler } from 'es6-crawler-detect';
+
+function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new T [...]
+
+function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
+
+function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
+var newTracker = function newTracker() {
+ var wem = {
+ enableWem: function enableWem() {
+ wem._enableWem(true);
+ },
+ disableWem: function disableWem() {
+ wem._enableWem(false);
+ },
+
+ /**
+ * This function initialize the context in the page it is called internally and should not be called twice in the same page
+ *
+ * @param {object} digitalData config of the tracker
+ */
+ init: function init(digitalData) {
+ // added for external tracker
+ // store digitalData in tracker instance instead of window.
+ wem.digitalData = digitalData; // new conf:
+
+ wem.trackerProfileIdCookieName = wem.digitalData.wemInitConfig.trackerProfileIdCookieName ? wem.digitalData.wemInitConfig.trackerProfileIdCookieName : "wem-profile-id";
+ wem.trackerSessionIdCookieName = wem.digitalData.wemInitConfig.trackerSessionIdCookieName ? wem.digitalData.wemInitConfig.trackerSessionIdCookieName : "wem-session-id";
+ wem.activateWem = wem.digitalData.wemInitConfig.activateWem;
+ var _wem$digitalData$wemI = wem.digitalData.wemInitConfig,
+ contextServerUrl = _wem$digitalData$wemI.contextServerUrl,
+ isPreview = _wem$digitalData$wemI.isPreview,
+ timeoutInMilliseconds = _wem$digitalData$wemI.timeoutInMilliseconds,
+ contextServerCookieName = _wem$digitalData$wemI.contextServerCookieName;
+ wem.contextServerCookieName = contextServerCookieName;
+ wem.contextServerUrl = contextServerUrl;
+ wem.timeoutInMilliseconds = timeoutInMilliseconds;
+ wem.formNamesToWatch = [];
+ wem.eventsPrevented = [];
+ wem.sessionID = wem.getCookie(wem.trackerSessionIdCookieName);
+ wem.fallback = false;
+
+ if (wem.sessionID === null) {
+ console.warn('[WEM] sessionID is null !');
+ } else if (!wem.sessionID || wem.sessionID === '') {
+ console.warn('[WEM] empty sessionID, setting to null !');
+ wem.sessionID = null;
+ }
+
+ if (isPreview) {
+ // do not execute fallback for preview!
+ return;
+ }
+
+ var cookieDisabled = !navigator.cookieEnabled;
+ var noSessionID = !wem.sessionID || wem.sessionID === '';
+ var crawlerDetected = navigator.userAgent;
+
+ if (crawlerDetected) {
+ var browserDetector = new Crawler();
+ crawlerDetected = browserDetector.isCrawler(navigator.userAgent);
+ }
+
+ if (cookieDisabled || noSessionID || crawlerDetected) {
+ document.addEventListener('DOMContentLoaded', function () {
+ wem._executeFallback('navigator cookie disabled: ' + cookieDisabled + ', no sessionID: ' + noSessionID + ', web crawler detected: ' + crawlerDetected);
+ });
+ return;
+ }
+
+ wem._registerCallback(function () {
+ if (wem.cxs.profileId) {
+ wem.setCookie(wem.trackerProfileIdCookieName, wem.cxs.profileId);
+ }
+
+ if (!wem.cxs.profileId) {
+ wem.removeCookie(wem.trackerProfileIdCookieName);
+ } // process tracked events
+
+
+ var videoNamesToWatch = [];
+ var clickToWatch = [];
+
+ if (wem.cxs.trackedConditions && wem.cxs.trackedConditions.length > 0) {
+ for (var i = 0; i < wem.cxs.trackedConditions.length; i++) {
+ switch (wem.cxs.trackedConditions[i].type) {
+ case 'formEventCondition':
+ if (wem.cxs.trackedConditions[i].parameterValues && wem.cxs.trackedConditions[i].parameterValues.formId) {
+ wem.formNamesToWatch.push(wem.cxs.trackedConditions[i].parameterValues.formId);
+ }
+
+ break;
+
+ case 'videoViewEventCondition':
+ if (wem.cxs.trackedConditions[i].parameterValues && wem.cxs.trackedConditions[i].parameterValues.videoId) {
+ videoNamesToWatch.push(wem.cxs.trackedConditions[i].parameterValues.videoId);
+ }
+
+ break;
+
+ case 'clickOnLinkEventCondition':
+ if (wem.cxs.trackedConditions[i].parameterValues && wem.cxs.trackedConditions[i].parameterValues.itemId) {
+ clickToWatch.push(wem.cxs.trackedConditions[i].parameterValues.itemId);
+ }
+
+ break;
+ }
+ }
+ }
+
+ var forms = document.querySelectorAll('form');
+
+ for (var formIndex = 0; formIndex < forms.length; formIndex++) {
+ var form = forms.item(formIndex);
+ var formName = form.getAttribute('name') ? form.getAttribute('name') : form.getAttribute('id'); // test attribute data-form-id to not add a listener on FF form
+
+ if (formName && wem.formNamesToWatch.indexOf(formName) > -1 && form.getAttribute('data-form-id') == null) {
+ // add submit listener on form that we need to watch only
+ console.info('[WEM] watching form ' + formName);
+ form.addEventListener('submit', wem._formSubmitEventListener, true);
+ }
+ }
+
+ for (var videoIndex = 0; videoIndex < videoNamesToWatch.length; videoIndex++) {
+ var videoName = videoNamesToWatch[videoIndex];
+ var video = document.getElementById(videoName) || document.getElementById(wem._resolveId(videoName));
+
+ if (video) {
+ video.addEventListener('play', wem.sendVideoEvent);
+ video.addEventListener('ended', wem.sendVideoEvent);
+ console.info('[WEM] watching video ' + videoName);
+ } else {
+ console.warn('[WEM] unable to watch video ' + videoName + ', video not found in the page');
+ }
+ }
+
+ for (var clickIndex = 0; clickIndex < clickToWatch.length; clickIndex++) {
+ var clickIdName = clickToWatch[clickIndex];
+ var click = document.getElementById(clickIdName) || document.getElementById(wem._resolveId(clickIdName)) ? document.getElementById(clickIdName) || document.getElementById(wem._resolveId(clickIdName)) : document.getElementsByName(clickIdName)[0];
+
+ if (click) {
+ click.addEventListener('click', wem.sendClickEvent);
+ console.info('[WEM] watching click ' + clickIdName);
+ } else {
+ console.warn('[WEM] unable to watch click ' + clickIdName + ', element not found in the page');
+ }
+ }
+ }); // Load the context once document is ready
+
+
+ document.addEventListener('DOMContentLoaded', function () {
+ wem.DOMLoaded = true; // complete already registered events
+
+ wem._checkUncompleteRegisteredEvents(); // Dispatch javascript events for the experience (perso/opti displayed from SSR, based on unomi events)
+
+
+ wem._dispatchJSExperienceDisplayedEvents(); // Some event may not need to be send to unomi, check for them and filter them out.
+
+
+ wem._filterUnomiEvents(); // Add referrer info into digitalData.page object.
+
+
+ wem._processReferrer(); // Build view event
+
+
+ var viewEvent = wem.buildEvent('view', wem.buildTargetPage(), wem.buildSource(wem.digitalData.site.siteInfo.siteID, 'site'));
+ viewEvent.flattenedProperties = {}; // Add URLParameters
+
+ if (location.search) {
+ viewEvent.flattenedProperties['URLParameters'] = wem.convertUrlParametersToObj(location.search);
+ } // Add interests
+
+
+ if (wem.digitalData.interests) {
+ viewEvent.flattenedProperties['interests'] = wem.digitalData.interests;
+ } // Register the page view event, it's unshift because it should be the first event, this is just for logical purpose. (page view comes before perso displayed event for example)
+
+
+ wem._registerEvent(viewEvent, true);
+
+ if (wem.activateWem) {
+ wem.loadContext();
+ } else {
+ wem._executeFallback('wem is not activated on current page');
+ }
+ });
+ },
+ convertUrlParametersToObj: function convertUrlParametersToObj(searchString) {
+ if (!searchString) {
+ return null;
+ }
+
+ return searchString.replace(/^\?/, '') // Only trim off a single leading interrobang.
+ .split('&').reduce(function (result, next) {
+ if (next === '') {
+ return result;
+ }
+
+ var pair = next.split('=');
+ var key = decodeURIComponent(pair[0]);
+ var value = typeof pair[1] !== 'undefined' && decodeURIComponent(pair[1]) || undefined;
+
+ if (Object.prototype.hasOwnProperty.call(result, key)) {
+ // Check to see if this property has been met before.
+ if (Array.isArray(result[key])) {
+ // Is it already an array?
+ result[key].push(value);
+ } else {
+ // Make it an array.
+ result[key] = [result[key], value];
+ }
+ } else {
+ // First time seen, just add it.
+ result[key] = value;
+ }
+
+ return result;
+ }, {});
+ },
+
+ /**
+ * This function will register a personalization
+ *
+ * @param {object} personalization
+ * @param {object} variants
+ * @param {boolean} [ajax] Deprecated: Ajax rendering is not supported anymore
+ * @param {function} [resultCallback]
+ */
+ registerPersonalizationObject: function registerPersonalizationObject(personalization, variants, ajax, resultCallback) {
+ var target = personalization.id;
+
+ wem._registerPersonalizationCallback(personalization, function (result) {
+ var successfulFilters = [];
+
+ for (var i = 0; i < result.length; i++) {
+ successfulFilters.push(variants[result[i]]);
+ }
+
+ var selectedFilter = null;
+
+ if (successfulFilters.length > 0) {
+ selectedFilter = successfulFilters[0];
+ var minPos = successfulFilters[0].position;
+
+ if (minPos >= 0) {
+ for (var j = 1; j < successfulFilters.length; j++) {
+ if (successfulFilters[j].position < minPos) {
+ selectedFilter = successfulFilters[j];
+ }
+ }
+ }
+ }
+
+ if (resultCallback) {
+ // execute callback
+ resultCallback(successfulFilters, selectedFilter);
+ } else {
+ if (selectedFilter) {
+ var targetFilters = document.getElementById(target).children;
+
+ for (var fIndex in targetFilters) {
+ var filter = targetFilters.item(fIndex);
+
+ if (filter) {
+ filter.style.display = filter.id === selectedFilter.content ? '' : 'none';
+ }
+ } // we now add control group information to event if the user is in the control group.
+
+
+ if (wem._isInControlGroup(target)) {
+ console.info('[WEM] Profile is in control group for target: ' + target + ', adding to personalization event...');
+ selectedFilter.event.target.properties.inControlGroup = true;
+
+ if (selectedFilter.event.target.properties.variants) {
+ selectedFilter.event.target.properties.variants.forEach(function (variant) {
+ return variant.inControlGroup = true;
+ });
+ }
+ } // send event to unomi
+
+
+ wem.collectEvent(wem._completeEvent(selectedFilter.event), function () {
+ console.info('[WEM] Personalization event successfully collected.');
+ }, function () {
+ console.error('[WEM] Could not send personalization event.');
+ }); //Trigger variant display event for personalization
+
+ wem._dispatchJSExperienceDisplayedEvent(selectedFilter.event);
+ } else {
+ var elements = document.getElementById(target).children;
+
+ for (var eIndex in elements) {
+ var el = elements.item(eIndex);
+ el.style.display = 'none';
+ }
+ }
+ }
+ });
+ },
+
+ /**
+ * This function will register an optimization test or A/B test
+ *
+ * @param {string} optimizationTestNodeId
+ * @param {string} goalId
+ * @param {string} containerId
+ * @param {object} variants
+ * @param {boolean} [ajax] Deprecated: Ajax rendering is not supported anymore
+ * @param {object} [variantsTraffic]
+ */
+ registerOptimizationTest: function registerOptimizationTest(optimizationTestNodeId, goalId, containerId, variants, ajax, variantsTraffic) {
+ // check persona panel forced variant
+ var selectedVariantId = wem.getUrlParameter('wemSelectedVariantId-' + optimizationTestNodeId); // check already resolved variant stored in local
+
+ if (selectedVariantId === null) {
+ if (wem.storageAvailable('sessionStorage')) {
+ selectedVariantId = sessionStorage.getItem(optimizationTestNodeId);
+ } else {
+ selectedVariantId = wem.getCookie('selectedVariantId');
+
+ if (selectedVariantId != null && selectedVariantId === '') {
+ selectedVariantId = null;
+ }
+ }
+ } // select random variant and call unomi
+
+
+ if (!(selectedVariantId && variants[selectedVariantId])) {
+ var keys = Object.keys(variants);
+
+ if (variantsTraffic) {
+ var rand = 100 * Math.random() << 0;
+
+ for (var nodeIdentifier in variantsTraffic) {
+ if ((rand -= variantsTraffic[nodeIdentifier]) < 0 && selectedVariantId == null) {
+ selectedVariantId = nodeIdentifier;
+ }
+ }
+ } else {
+ selectedVariantId = keys[keys.length * Math.random() << 0];
+ }
+
+ if (wem.storageAvailable('sessionStorage')) {
+ sessionStorage.setItem(optimizationTestNodeId, selectedVariantId);
+ } else {
+ wem.setCookie('selectedVariantId', selectedVariantId, 1);
+ } // spread event to unomi
+
+
+ wem._registerEvent(wem._completeEvent(variants[selectedVariantId].event));
+ } //Trigger variant display event for optimization
+ // (Wrapped in DOMContentLoaded because opti are resulted synchronously at page load, so we dispatch the JS even after page load, to be sure that listeners are ready)
+
+
+ window.addEventListener('DOMContentLoaded', function () {
+ wem._dispatchJSExperienceDisplayedEvent(variants[selectedVariantId].event);
+ });
+
+ if (selectedVariantId) {
+ // update persona panel selected variant
+ if (window.optimizedContentAreas && window.optimizedContentAreas[optimizationTestNodeId]) {
+ window.optimizedContentAreas[optimizationTestNodeId].selectedVariant = selectedVariantId;
+ } // display the good variant
+
+
+ document.getElementById(variants[selectedVariantId].content).style.display = '';
+ }
+ },
+
+ /**
+ * This function is used to load the current context in the page
+ *
+ * @param {boolean} [skipEvents=false] Should we send the events
+ * @param {boolean} [invalidate=false] Should we invalidate the current context
+ */
+ loadContext: function loadContext(skipEvents, invalidate) {
+ if (wem.contextLoaded) {
+ console.log('Context already requested by', wem.contextLoaded);
+ return;
+ }
+
+ var jsonData = {
+ requiredProfileProperties: wem.digitalData.wemInitConfig.requiredProfileProperties,
+ requiredSessionProperties: wem.digitalData.wemInitConfig.requiredSessionProperties,
+ requireSegments: wem.digitalData.wemInitConfig.requireSegments,
+ requireScores: wem.digitalData.wemInitConfig.requireScores,
+ source: wem.buildSourcePage()
+ };
+
+ if (!skipEvents) {
+ jsonData.events = wem.digitalData.events;
+ }
+
+ if (wem.digitalData.personalizationCallback) {
+ jsonData.personalizations = wem.digitalData.personalizationCallback.map(function (x) {
+ return x.personalization;
+ });
+ }
+
+ jsonData.sessionId = wem.sessionID;
+ var contextUrl = wem.contextServerUrl + '/context.json';
+
+ if (invalidate) {
+ contextUrl += '?invalidateSession=true&invalidateProfile=true';
+ }
+
+ wem.ajax({
+ url: contextUrl,
+ type: 'POST',
+ async: true,
+ contentType: 'text/plain;charset=UTF-8',
+ // Use text/plain to avoid CORS preflight
+ jsonData: jsonData,
+ dataType: 'application/json',
+ invalidate: invalidate,
+ success: wem._onSuccess,
+ error: function error() {
+ wem._executeFallback('error during context loading');
+ }
+ });
+ wem.contextLoaded = Error().stack;
+ console.info('[WEM] context loading...');
+ },
+
+ /**
+ * This function will send an event to Apache Unomi
+ * @param {object} event The event object to send, you can build it using wem.buildEvent(eventType, target, source)
+ * @param {function} successCallback will be executed in case of success
+ * @param {function} errorCallback will be executed in case of error
+ */
+ collectEvent: function collectEvent(event, successCallback, errorCallback) {
+ wem.collectEvents({
+ events: [event]
+ }, successCallback, errorCallback);
+ },
+
+ /**
+ * This function will send the events to Apache Unomi
+ *
+ * @param {object} events Javascript object { events: [event1, event2] }
+ * @param {function} successCallback will be executed in case of success
+ * @param {function} errorCallback will be executed in case of error
+ */
+ collectEvents: function collectEvents(events, successCallback, errorCallback) {
+ if (wem.fallback) {
+ // in case of fallback we dont want to collect any events
+ return;
+ }
+
+ events.sessionId = wem.sessionID ? wem.sessionID : '';
+ var data = JSON.stringify(events);
+ wem.ajax({
+ url: wem.contextServerUrl + '/eventcollector',
+ type: 'POST',
+ async: true,
+ contentType: 'text/plain;charset=UTF-8',
+ // Use text/plain to avoid CORS preflight
+ data: data,
+ dataType: 'application/json',
+ success: successCallback,
+ error: errorCallback
+ });
+ },
+
+ /**
+ * This function will build an event of type click and send it to Apache Unomi
+ *
+ * @param {object} event javascript
+ * @param {function} [successCallback] will be executed if case of success
+ * @param {function} [errorCallback] will be executed if case of error
+ */
+ sendClickEvent: function sendClickEvent(event, successCallback, errorCallback) {
+ if (event.target.id || event.target.name) {
+ console.info('[WEM] Send click event');
+ var targetId = event.target.id ? event.target.id : event.target.name;
+ var clickEvent = wem.buildEvent('click', wem.buildTarget(targetId, event.target.localName), wem.buildSourcePage());
+ var eventIndex = wem.eventsPrevented.indexOf(targetId);
+
+ if (eventIndex !== -1) {
+ wem.eventsPrevented.splice(eventIndex, 0);
+ } else {
+ wem.eventsPrevented.push(targetId);
+ event.preventDefault();
+ var target = event.target;
+ wem.collectEvent(clickEvent, function (xhr) {
+ console.info('[WEM] Click event successfully collected.');
+
+ if (successCallback) {
+ successCallback(xhr);
+ } else {
+ target.click();
+ }
+ }, function (xhr) {
+ console.error('[WEM] Could not send click event.');
+
+ if (errorCallback) {
+ errorCallback(xhr);
+ } else {
+ target.click();
+ }
+ });
+ }
+ }
+ },
+
+ /**
+ * This function will build an event of type video and send it to Apache Unomi
+ *
+ * @param {object} event javascript
+ * @param {function} [successCallback] will be executed if case of success
+ * @param {function} [errorCallback] will be executed if case of error
+ */
+ sendVideoEvent: function sendVideoEvent(event, successCallback, errorCallback) {
+ console.info('[WEM] catching video event');
+ var videoEvent = wem.buildEvent('video', wem.buildTarget(event.target.id, 'video', {
+ action: event.type
+ }), wem.buildSourcePage());
+ wem.collectEvent(videoEvent, function (xhr) {
+ console.info('[WEM] Video event successfully collected.');
+
+ if (successCallback) {
+ successCallback(xhr);
+ }
+ }, function (xhr) {
+ console.error('[WEM] Could not send video event.');
+
+ if (errorCallback) {
+ errorCallback(xhr);
+ }
+ });
+ },
+
+ /**
+ * This function return the basic structure for an event, it must be adapted to your need
+ *
+ * @param {string} eventType The name of your event
+ * @param {object} [target] The target object for your event can be build with wem.buildTarget(targetId, targetType, targetProperties)
+ * @param {object} [source] The source object for your event can be build with wem.buildSource(sourceId, sourceType, sourceProperties)
+ * @returns {{eventType: *, scope}}
+ */
+ buildEvent: function buildEvent(eventType, target, source) {
+ var event = {
+ eventType: eventType,
+ scope: wem.digitalData.scope
+ };
+
+ if (target) {
+ event.target = target;
+ }
+
+ if (source) {
+ event.source = source;
+ }
+
+ return event;
+ },
+
+ /**
+ * This function return an event of type form
+ *
+ * @param {string} formName The HTML name of id of the form to use in the target of the event
+ * @returns {*|{eventType: *, scope, source: {scope, itemId: string, itemType: string, properties: {}}, target: {scope, itemId: string, itemType: string, properties: {}}}}
+ */
+ buildFormEvent: function buildFormEvent(formName) {
+ return wem.buildEvent('form', wem.buildTarget(formName, 'form'), wem.buildSourcePage());
+ },
+
+ /**
+ * This function return the source object for a source of type page
+ *
+ * @returns {*|{scope, itemId: *, itemType: *}}
+ */
+ buildTargetPage: function buildTargetPage() {
+ return wem.buildTarget(wem.digitalData.page.pageInfo.pageID, 'page', wem.digitalData.page);
+ },
+
+ /**
+ * This function return the source object for a source of type page
+ *
+ * @returns {*|{scope, itemId: *, itemType: *}}
+ */
+ buildSourcePage: function buildSourcePage() {
+ return wem.buildSource(wem.digitalData.page.pageInfo.pageID, 'page', wem.digitalData.page);
+ },
+
+ /**
+ * This function return the basic structure for the target of your event
+ *
+ * @param {string} targetId The ID of the target
+ * @param {string} targetType The type of the target
+ * @param {object} [targetProperties] The optional properties of the target
+ * @returns {{scope, itemId: *, itemType: *}}
+ */
+ buildTarget: function buildTarget(targetId, targetType, targetProperties) {
+ return wem._buildObject(targetId, targetType, targetProperties);
+ },
+
+ /**
+ * This function return the basic structure for the source of your event
+ *
+ * @param {string} sourceId The ID of the source
+ * @param {string} sourceType The type of the source
+ * @param {object} [sourceProperties] The optional properties of the source
+ * @returns {{scope, itemId: *, itemType: *}}
+ */
+ buildSource: function buildSource(sourceId, sourceType, sourceProperties) {
+ return wem._buildObject(sourceId, sourceType, sourceProperties);
+ },
+
+ /*************************************/
+
+ /* Utility functions under this line */
+
+ /*************************************/
+
+ /**
+ * This is an utility function to set a cookie
+ *
+ * @param {string} cookieName name of the cookie
+ * @param {string} cookieValue value of the cookie
+ * @param {number} [expireDays] number of days to set the expire date
+ */
+ setCookie: function setCookie(cookieName, cookieValue, expireDays) {
+ var expires = '';
+
+ if (expireDays) {
+ var d = new Date();
+ d.setTime(d.getTime() + expireDays * 24 * 60 * 60 * 1000);
+ expires = '; expires=' + d.toUTCString();
+ }
+
+ document.cookie = cookieName + '=' + cookieValue + expires + '; path=/; SameSite=Strict';
+ },
+
+ /**
+ * This is an utility function to get a cookie
+ *
+ * @param {string} cookieName name of the cookie to get
+ * @returns {*} the value of the first cookie with the corresponding name or null if not found
+ */
+ getCookie: function getCookie(cookieName) {
+ var name = cookieName + '=';
+ var ca = document.cookie.split(';');
+
+ for (var i = 0; i < ca.length; i++) {
+ var c = ca[i];
+
+ while (c.charAt(0) == ' ') {
+ c = c.substring(1);
+ }
+
+ if (c.indexOf(name) == 0) {
+ return c.substring(name.length, c.length);
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * This is an utility function to remove a cookie
+ *
+ * @param {string} cookieName the name of the cookie to rename
+ */
+ removeCookie: function removeCookie(cookieName) {
+
+ wem.setCookie(cookieName, '', -1);
+ },
+
+ /**
+ * This is an utility function to execute AJAX call
+ *
+ * @param {object} options
+ */
+ ajax: function ajax(options) {
+ var xhr = new XMLHttpRequest();
+
+ if ('withCredentials' in xhr) {
+ xhr.open(options.type, options.url, options.async);
+ xhr.withCredentials = true;
+ } else if (typeof XDomainRequest != 'undefined') {
+ /* global XDomainRequest */
+ xhr = new XDomainRequest();
+ xhr.open(options.type, options.url);
+ }
+
+ if (options.contentType) {
+ xhr.setRequestHeader('Content-Type', options.contentType);
+ }
+
+ if (options.dataType) {
+ xhr.setRequestHeader('Accept', options.dataType);
+ }
+
+ if (options.responseType) {
+ xhr.responseType = options.responseType;
+ }
+
+ var requestExecuted = false;
+
+ if (wem.timeoutInMilliseconds !== -1) {
+ setTimeout(function () {
+ if (!requestExecuted) {
+ console.error('[WEM] XML request timeout, url: ' + options.url);
+ requestExecuted = true;
+
+ if (options.error) {
+ options.error(xhr);
+ }
+ }
+ }, wem.timeoutInMilliseconds);
+ }
+
+ xhr.onreadystatechange = function () {
+ if (!requestExecuted) {
+ if (xhr.readyState === 4) {
+ if (xhr.status === 200 || xhr.status === 204 || xhr.status === 304) {
+ if (xhr.responseText != null) {
+ requestExecuted = true;
+
+ if (options.success) {
+ options.success(xhr);
+ }
+ }
+ } else {
+ requestExecuted = true;
+
+ if (options.error) {
+ options.error(xhr);
+ }
+
+ console.error('[WEM] XML request error: ' + xhr.statusText + ' (' + xhr.status + ')');
+ }
+ }
+ }
+ };
+
+ if (options.jsonData) {
+ xhr.send(JSON.stringify(options.jsonData));
+ } else if (options.data) {
+ xhr.send(options.data);
+ } else {
+ xhr.send();
+ }
+ },
+
+ /**
+ * This is an utility function to check if the local storage is available or not
+ * @param type
+ * @returns {boolean}
+ */
+ storageAvailable: function storageAvailable(type) {
+ try {
+ var storage = window[type],
+ x = '__storage_test__';
+ storage.setItem(x, x);
+ storage.removeItem(x);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ },
+ dispatchJSEvent: function dispatchJSEvent(name, canBubble, cancelable, detail) {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent(name, canBubble, cancelable, detail);
+ document.dispatchEvent(event);
+ },
+
+ /**
+ * This is an utility function to get current url parameter value
+ * @param name, the name of the parameter
+ * @returns {string}
+ */
+ getUrlParameter: function getUrlParameter(name) {
+ name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
+ var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
+ var results = regex.exec(window.location.search);
+ return results === null ? null : decodeURIComponent(results[1].replace(/\+/g, ' '));
+ },
+
+ /*************************************/
+
+ /* Private functions under this line */
+
+ /*************************************/
+ _checkUncompleteRegisteredEvents: function _checkUncompleteRegisteredEvents() {
+ if (wem.digitalData && wem.digitalData.events) {
+ var _iterator = _createForOfIteratorHelper(wem.digitalData.events),
+ _step;
+
+ try {
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
+ var event = _step.value;
+
+ wem._completeEvent(event);
+ }
+ } catch (err) {
+ _iterator.e(err);
+ } finally {
+ _iterator.f();
+ }
+ }
+ },
+ _dispatchJSExperienceDisplayedEvents: function _dispatchJSExperienceDisplayedEvents() {
+ if (wem.digitalData && wem.digitalData.events) {
+ var _iterator2 = _createForOfIteratorHelper(wem.digitalData.events),
+ _step2;
+
+ try {
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
+ var event = _step2.value;
+
+ if (event.eventType === 'optimizationTestEvent' || event.eventType === 'personalizationEvent') {
+ wem._dispatchJSExperienceDisplayedEvent(event);
+ }
+ }
+ } catch (err) {
+ _iterator2.e(err);
+ } finally {
+ _iterator2.f();
+ }
+ }
+ },
+ _dispatchJSExperienceDisplayedEvent: function _dispatchJSExperienceDisplayedEvent(experienceUnomiEvent) {
+ if (!wem.fallback && experienceUnomiEvent && experienceUnomiEvent.target && experienceUnomiEvent.target.properties && experienceUnomiEvent.target.properties.variants && experienceUnomiEvent.target.properties.variants.length > 0) {
+ var typeMapper = {
+ optimizationTestEvent: 'optimization',
+ personalizationEvent: 'personalization'
+ };
+
+ var _iterator3 = _createForOfIteratorHelper(experienceUnomiEvent.target.properties.variants),
+ _step3;
+
+ try {
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
+ var variant = _step3.value;
+ var jsEventDetail = {
+ id: variant.id,
+ name: variant.systemName,
+ displayableName: variant.displayableName,
+ path: variant.path,
+ type: typeMapper[experienceUnomiEvent.eventType],
+ variantType: experienceUnomiEvent.target.properties.type,
+ tags: variant.tags,
+ nodeType: variant.nodeType,
+ wrapper: {
+ id: experienceUnomiEvent.target.itemId,
+ name: experienceUnomiEvent.target.properties.systemName,
+ displayableName: experienceUnomiEvent.target.properties.displayableName,
+ path: experienceUnomiEvent.target.properties.path,
+ tags: experienceUnomiEvent.target.properties.tags,
+ nodeType: experienceUnomiEvent.target.properties.nodeType
+ }
+ };
+ wem.dispatchJSEvent('displayWemVariant', false, false, jsEventDetail);
+ }
+ } catch (err) {
+ _iterator3.e(err);
+ } finally {
+ _iterator3.f();
+ }
+ }
+ },
+ _filterUnomiEvents: function _filterUnomiEvents() {
+ if (wem.digitalData && wem.digitalData.events) {
+ wem.digitalData.events = wem.digitalData.events.filter(function (event) {
+ return !event.properties || !event.properties.doNotSendToUnomi;
+ }).map(function (event) {
+ if (event.properties) {
+ delete event.properties.doNotSendToUnomi;
+ }
+
+ return event;
+ });
+ }
+ },
+ _completeEvent: function _completeEvent(event) {
+ if (!event.source) {
+ event.source = wem.buildSourcePage();
+ }
+
+ if (!event.scope) {
+ event.scope = wem.digitalData.scope;
+ }
+
+ if (event.target && !event.target.scope) {
+ event.target.scope = wem.digitalData.scope;
+ }
+
+ return event;
+ },
+ _registerEvent: function _registerEvent(event, unshift) {
+ if (wem.digitalData) {
+ if (wem.cxs) {
+ console.error('[WEM] already loaded, too late...');
+ return;
+ }
+ } else {
+ wem.digitalData = {};
+ }
+
+ wem.digitalData.events = wem.digitalData.events || [];
+
+ if (unshift) {
+ wem.digitalData.events.unshift(event);
+ } else {
+ wem.digitalData.events.push(event);
+ }
+ },
+ _registerCallback: function _registerCallback(onLoadCallback) {
+ if (wem.digitalData) {
+ if (wem.cxs) {
+ console.info('[WEM] digitalData object loaded, calling on load callback immediately and registering update callback...');
+
+ if (onLoadCallback) {
+ onLoadCallback(wem.digitalData);
+ }
+ } else {
+ console.info('[WEM] digitalData object present but not loaded, registering load callback...');
+
+ if (onLoadCallback) {
+ wem.digitalData.loadCallbacks = wem.digitalData.loadCallbacks || [];
+ wem.digitalData.loadCallbacks.push(onLoadCallback);
+ }
+ }
+ } else {
+ console.info('[WEM] No digital data object found, creating and registering update callback...');
+ wem.digitalData = {};
+
+ if (onLoadCallback) {
+ wem.digitalData.loadCallbacks = [];
+ wem.digitalData.loadCallbacks.push(onLoadCallback);
+ }
+ }
+ },
+ _registerPersonalizationCallback: function _registerPersonalizationCallback(personalization, callback) {
+ if (wem.digitalData) {
+ if (wem.cxs) {
+ console.error('[WEM] already loaded, too late...');
+ } else {
+ console.info('[WEM] digitalData object present but not loaded, registering sort callback...');
+ wem.digitalData.personalizationCallback = wem.digitalData.personalizationCallback || [];
+ wem.digitalData.personalizationCallback.push({
+ personalization: personalization,
+ callback: callback
+ });
+ }
+ } else {
+ wem.digitalData = {};
+ wem.digitalData.personalizationCallback = wem.digitalData.personalizationCallback || [];
+ wem.digitalData.personalizationCallback.push({
+ personalization: personalization,
+ callback: callback
+ });
+ }
+ },
+ _buildObject: function _buildObject(itemId, itemType, properties) {
+ var object = {
+ scope: wem.digitalData.scope,
+ itemId: itemId,
+ itemType: itemType
+ };
+
+ if (properties) {
+ object.properties = properties;
+ }
+
+ return object;
+ },
+ _onSuccess: function _onSuccess(xhr) {
+ wem.cxs = JSON.parse(xhr.responseText);
+
+ if (wem.digitalData.loadCallbacks && wem.digitalData.loadCallbacks.length > 0) {
+ console.info('[WEM] Found context server load callbacks, calling now...');
+
+ if (wem.digitalData.loadCallbacks) {
+ for (var i = 0; i < wem.digitalData.loadCallbacks.length; i++) {
+ wem.digitalData.loadCallbacks[i](wem.digitalData);
+ }
+ }
+
+ if (wem.digitalData.personalizationCallback) {
+ for (var j = 0; j < wem.digitalData.personalizationCallback.length; j++) {
+ wem.digitalData.personalizationCallback[j].callback(wem.cxs.personalizations[wem.digitalData.personalizationCallback[j].personalization.id]);
+ }
+ }
+ } // Put a marker to be able to know when wem is full loaded, context is loaded, and callbacks have been executed.
+
+
+ window.wemLoaded = true;
+ },
+ _executeFallback: function _executeFallback(logMessage) {
+ console.warn('[WEM] execute fallback' + (logMessage ? ': ' + logMessage : ''));
+ wem.fallback = true;
+ wem.cxs = {};
+
+ for (var index in wem.digitalData.loadCallbacks) {
+ wem.digitalData.loadCallbacks[index]();
+ }
+
+ if (wem.digitalData.personalizationCallback) {
+ for (var i = 0; i < wem.digitalData.personalizationCallback.length; i++) {
+ wem.digitalData.personalizationCallback[i].callback([wem.digitalData.personalizationCallback[i].personalization.strategyOptions.fallback]);
+ }
+ }
+ },
+ _processReferrer: function _processReferrer() {
+ var referrerURL = wem.digitalData.page.pageInfo.referringURL || document.referrer;
+ var sameDomainReferrer = false;
+
+ if (referrerURL) {
+ // parse referrer URL
+ var referrer = new URL(referrerURL); // Set sameDomainReferrer property
+
+ sameDomainReferrer = referrer.host === window.location.host; // only process referrer if it's not coming from the same site as the current page
+
+ if (!sameDomainReferrer) {
+ // get search element if it exists and extract search query if available
+ var search = referrer.search;
+ var query = undefined;
+
+ if (search && search != '') {
+ // parse parameters
+ var queryParams = [],
+ param;
+ var queryParamPairs = search.slice(1).split('&');
+
+ for (var i = 0; i < queryParamPairs.length; i++) {
+ param = queryParamPairs[i].split('=');
+ queryParams.push(param[0]);
+ queryParams[param[0]] = param[1];
+ } // try to extract query: q is Google-like (most search engines), p is Yahoo
+
+
+ query = queryParams.q || queryParams.p;
+ query = decodeURIComponent(query).replace(/\+/g, ' ');
+ } // register referrer event
+ // Create deep copy of wem.digitalData.page and add data to pageInfo sub object
+
+
+ if (wem.digitalData && wem.digitalData.page && wem.digitalData.page.pageInfo) {
+ wem.digitalData.page.pageInfo.referrerHost = referrer.host;
+ wem.digitalData.page.pageInfo.referrerQuery = query;
+ }
+ }
+ }
+
+ wem.digitalData.page.pageInfo.sameDomainReferrer = sameDomainReferrer;
+ },
+ _formSubmitEventListener: function _formSubmitEventListener(event) {
+ console.info('[WEM] Registering form event callback');
+ var form = event.target;
+ var formName = form.getAttribute('name') ? form.getAttribute('name') : form.getAttribute('id');
+
+ if (formName && wem.formNamesToWatch.indexOf(formName) > -1) {
+ console.info('[WEM] catching form ' + formName);
+ var eventCopy = document.createEvent('Event'); // Define that the event name is 'build'.
+
+ eventCopy.initEvent('submit', event.bubbles, event.cancelable);
+ event.stopImmediatePropagation();
+ event.preventDefault();
+ var formEvent = wem.buildFormEvent(formName); // merge form properties with event properties
+
+ formEvent.flattenedProperties = {
+ fields: wem._extractFormData(form)
+ };
+ wem.collectEvent(formEvent, function () {
+ form.removeEventListener('submit', wem._formSubmitEventListener, true);
+ form.dispatchEvent(eventCopy);
+
+ if (!eventCopy.defaultPrevented && !eventCopy.cancelBubble) {
+ form.submit();
+ }
+
+ form.addEventListener('submit', wem._formSubmitEventListener, true);
+ }, function (xhr) {
+ console.error('[WEM] Error while collecting form event: ' + xhr.status + ' ' + xhr.statusText);
+ xhr.abort();
+ form.removeEventListener('submit', wem._formSubmitEventListener, true);
+ form.dispatchEvent(eventCopy);
+
+ if (!eventCopy.defaultPrevented && !eventCopy.cancelBubble) {
+ form.submit();
+ }
+
+ form.addEventListener('submit', wem._formSubmitEventListener, true);
+ });
+ }
+ },
+ _extractFormData: function _extractFormData(form) {
+ var params = {};
+
+ for (var i = 0; i < form.elements.length; i++) {
+ var e = form.elements[i]; // ignore empty and undefined key (e.name)
+
+ if (e.name) {
+ switch (e.nodeName) {
+ case 'TEXTAREA':
+ case 'INPUT':
+ switch (e.type) {
+ case 'checkbox':
+ var checkboxes = document.querySelectorAll('input[name="' + e.name + '"]');
+
+ if (checkboxes.length > 1) {
+ if (!params[e.name]) {
+ params[e.name] = [];
+ }
+
+ if (e.checked) {
+ params[e.name].push(e.value);
+ }
+ }
+
+ break;
+
+ case 'radio':
+ if (e.checked) {
+ params[e.name] = e.value;
+ }
+
+ break;
+
+ default:
+ if (!e.value || e.value == '') {
+ // ignore element if no value is provided
+ break;
+ }
+
+ params[e.name] = e.value;
+ }
+
+ break;
+
+ case 'SELECT':
+ if (e.options && e.options[e.selectedIndex]) {
+ if (e.multiple) {
+ params[e.name] = [];
+
+ for (var j = 0; j < e.options.length; j++) {
+ if (e.options[j].selected) {
+ params[e.name].push(e.options[j].value);
+ }
+ }
+ } else {
+ params[e.name] = e.options[e.selectedIndex].value;
+ }
+ }
+
+ break;
+ }
+ }
+ }
+
+ return params;
+ },
+ _resolveId: function _resolveId(id) {
+ if (wem.digitalData.sourceLocalIdentifierMap) {
+ var source = Object.keys(wem.digitalData.sourceLocalIdentifierMap).filter(function (source) {
+ return id.indexOf(source) > 0;
+ });
+ return source ? id.replace(source, wem.digitalData.sourceLocalIdentifierMap[source]) : id;
+ }
+
+ return id;
+ },
+ _enableWem: function _enableWem(enable, callback) {
+ // display fallback if wem is not enable
+ wem.fallback = !enable; // remove cookies, reset cxs
+
+ if (!enable) {
+ wem.cxs = {};
+ document.cookie = wem.trackerProfileIdCookieName + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
+ document.cookie = wem.contextServerCookieName + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
+ delete wem.contextLoaded;
+ } else {
+ if (wem.DOMLoaded) {
+ wem.loadContext();
+ } else {
+ // As Dom loaded listener not triggered, enable global value.
+ wem.activateWem = true;
+ }
+ }
+
+ if (callback) {
+ callback(enable);
+ }
+
+ console.log("Wem ".concat(enable ? 'enabled' : 'disabled'));
+ },
+ _isInControlGroup: function _isInControlGroup(id) {
+ if (wem.cxs.profileProperties && wem.cxs.profileProperties.unomiControlGroups) {
+ var controlGroup = wem.cxs.profileProperties.unomiControlGroups.find(function (controlGroup) {
+ return controlGroup.id === id;
+ });
+
+ if (controlGroup) {
+ return true;
+ }
+ }
+
+ if (wem.cxs.sessionProperties && wem.cxs.sessionProperties.unomiControlGroups) {
+ var _controlGroup = wem.cxs.sessionProperties.unomiControlGroups.find(function (controlGroup) {
+ return controlGroup.id === id;
+ });
+
+ if (_controlGroup) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ };
+ return wem;
+};
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var useTracker = function useTracker() {
+ return newTracker();
+};
+
+export { useTracker };
diff --git a/dist/apache-unomi-tracker.umd.js b/dist/apache-unomi-tracker.umd.js
new file mode 100644
index 0000000..51311ac
--- /dev/null
+++ b/dist/apache-unomi-tracker.umd.js
@@ -0,0 +1,2887 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["apache-unomi-tracker"] = {}));
+})(this, (function (exports) { 'use strict';
+
+ class Provider$3 {
+ constructor() {}
+
+ getAll() {
+ return this.data;
+ }
+ }
+
+ var provider = Provider$3;
+
+ const Provider$2 = provider;
+
+ class Crawlers$1 extends Provider$2 {
+ constructor() {
+ super();
+
+ this.data = [
+ ' YLT',
+ '^Aether',
+ '^Amazon Simple Notification Service Agent$',
+ '^Amazon-Route53-Health-Check-Service',
+ '^b0t$',
+ '^bluefish ',
+ '^Calypso v\\/',
+ '^COMODO DCV',
+ '^Corax',
+ '^DangDang',
+ '^DavClnt',
+ '^DHSH',
+ '^docker\\/[0-9]',
+ '^Expanse',
+ '^FDM ',
+ '^git\\/',
+ '^Goose\\/',
+ '^Grabber',
+ '^Gradle\\/',
+ '^HTTPClient\\/',
+ '^HTTPing',
+ '^Java\\/',
+ '^Jeode\\/',
+ '^Jetty\\/',
+ '^Mail\\/',
+ '^Mget',
+ '^Microsoft URL Control',
+ '^Mikrotik\\/',
+ '^Netlab360',
+ '^NG\\/[0-9\\.]',
+ '^NING\\/',
+ '^npm\\/',
+ '^Nuclei',
+ '^PHP-AYMAPI\\/',
+ '^PHP\\/',
+ '^pip\\/',
+ '^pnpm\\/',
+ '^RMA\\/',
+ '^Ruby|Ruby\\/[0-9]',
+ '^Swurl ',
+ '^TLS tester ',
+ '^twine\\/',
+ '^ureq',
+ '^VSE\\/[0-9]',
+ '^WordPress\\.com',
+ '^XRL\\/[0-9]',
+ '^ZmEu',
+ '008\\/',
+ '13TABS',
+ '192\\.comAgent',
+ '2GDPR\\/',
+ '2ip\\.ru',
+ '404enemy',
+ '7Siters',
+ '80legs',
+ 'a3logics\\.in',
+ 'A6-Indexer',
+ 'Abonti',
+ 'Aboundex',
+ 'aboutthedomain',
+ 'Accoona-AI-Agent',
+ 'acebookexternalhit\\/',
+ 'acoon',
+ 'acrylicapps\\.com\\/pulp',
+ 'Acunetix',
+ 'AdAuth\\/',
+ 'adbeat',
+ 'AddThis',
+ 'ADmantX',
+ 'AdminLabs',
+ 'adressendeutschland',
+ 'adreview\\/',
+ 'adscanner',
+ 'adstxt-worker',
+ 'Adstxtaggregator',
+ 'adstxt\\.com',
+ 'Adyen HttpClient',
+ 'AffiliateLabz\\/',
+ 'affilimate-puppeteer',
+ 'agentslug',
+ 'AHC',
+ 'aihit',
+ 'aiohttp\\/',
+ 'Airmail',
+ 'akka-http\\/',
+ 'akula\\/',
+ 'alertra',
+ 'alexa site audit',
+ 'Alibaba\\.Security\\.Heimdall',
+ 'Alligator',
+ 'allloadin',
+ 'AllSubmitter',
+ 'alyze\\.info',
+ 'amagit',
+ 'Anarchie',
+ 'AndroidDownloadManager',
+ 'Anemone',
+ 'AngleSharp',
+ 'annotate_google',
+ 'Anthill',
+ 'Anturis Agent',
+ 'Ant\\.com',
+ 'AnyEvent-HTTP\\/',
+ 'Apache Ant\\/',
+ 'Apache Droid',
+ 'Apache OpenOffice',
+ 'Apache-HttpAsyncClient',
+ 'Apache-HttpClient',
+ 'ApacheBench',
+ 'Apexoo',
+ 'apimon\\.de',
+ 'APIs-Google',
+ 'AportWorm\\/',
+ 'AppBeat\\/',
+ 'AppEngine-Google',
+ 'AppleSyndication',
+ 'Aprc\\/[0-9]',
+ 'Arachmo',
+ 'arachnode',
+ 'Arachnophilia',
+ 'aria2',
+ 'Arukereso',
+ 'asafaweb',
+ 'Asana\\/',
+ 'Ask Jeeves',
+ 'AskQuickly',
+ 'ASPSeek',
+ 'Asterias',
+ 'Astute',
+ 'asynchttp',
+ 'Attach',
+ 'attohttpc',
+ 'autocite',
+ 'AutomaticWPTester',
+ 'Autonomy',
+ 'awin\\.com',
+ 'AWS Security Scanner',
+ 'axios\\/',
+ 'a\\.pr-cy\\.ru',
+ 'B-l-i-t-z-B-O-T',
+ 'Backlink-Ceck',
+ 'backlink-check',
+ 'BacklinkHttpStatus',
+ 'BackStreet',
+ 'BackupLand',
+ 'BackWeb',
+ 'Bad-Neighborhood',
+ 'Badass',
+ 'baidu\\.com',
+ 'Bandit',
+ 'basicstate',
+ 'BatchFTP',
+ 'Battleztar Bazinga',
+ 'baypup\\/',
+ 'BazQux',
+ 'BBBike',
+ 'BCKLINKS',
+ 'BDFetch',
+ 'BegunAdvertising',
+ 'Bewica-security-scan',
+ 'Bidtellect',
+ 'BigBozz',
+ 'Bigfoot',
+ 'biglotron',
+ 'BingLocalSearch',
+ 'BingPreview',
+ 'binlar',
+ 'biNu image cacher',
+ 'Bitacle',
+ 'Bitrix link preview',
+ 'biz_Directory',
+ 'BKCTwitterUnshortener\\/',
+ 'Black Hole',
+ 'Blackboard Safeassign',
+ 'BlackWidow',
+ 'BlockNote\\.Net',
+ 'BlogBridge',
+ 'Bloglines',
+ 'Bloglovin',
+ 'BlogPulseLive',
+ 'BlogSearch',
+ 'Blogtrottr',
+ 'BlowFish',
+ 'boitho\\.com-dc',
+ 'Boost\\.Beast',
+ 'BPImageWalker',
+ 'Braintree-Webhooks',
+ 'Branch Metrics API',
+ 'Branch-Passthrough',
+ 'Brandprotect',
+ 'BrandVerity',
+ 'Brandwatch',
+ 'Brodie\\/',
+ 'Browsershots',
+ 'BUbiNG',
+ 'Buck\\/',
+ 'Buddy',
+ 'BuiltWith',
+ 'Bullseye',
+ 'BunnySlippers',
+ 'Burf Search',
+ 'Butterfly\\/',
+ 'BuzzSumo',
+ 'CAAM\\/[0-9]',
+ 'CakePHP',
+ 'Calculon',
+ 'Canary%20Mail',
+ 'CaretNail',
+ 'catexplorador',
+ 'CC Metadata Scaper',
+ 'Cegbfeieh',
+ 'censys',
+ 'centuryb.o.t9[at]gmail.com',
+ 'Cerberian Drtrs',
+ 'CERT\\.at-Statistics-Survey',
+ 'cf-facebook',
+ 'cg-eye',
+ 'changedetection',
+ 'ChangesMeter',
+ 'Charlotte',
+ 'CheckHost',
+ 'checkprivacy',
+ 'CherryPicker',
+ 'ChinaClaw',
+ 'Chirp\\/',
+ 'chkme\\.com',
+ 'Chlooe',
+ 'Chromaxa',
+ 'CirrusExplorer',
+ 'CISPA Vulnerability Notification',
+ 'CISPA Web Analyser',
+ 'Citoid',
+ 'CJNetworkQuality',
+ 'Clarsentia',
+ 'clips\\.ua\\.ac\\.be',
+ 'Cloud mapping',
+ 'CloudEndure',
+ 'CloudFlare-AlwaysOnline',
+ 'Cloudflare-Healthchecks',
+ 'Cloudinary',
+ 'cmcm\\.com',
+ 'coccoc',
+ 'cognitiveseo',
+ 'ColdFusion',
+ 'colly -',
+ 'CommaFeed',
+ 'Commons-HttpClient',
+ 'commonscan',
+ 'contactbigdatafr',
+ 'contentkingapp',
+ 'Contextual Code Sites Explorer',
+ 'convera',
+ 'CookieReports',
+ 'copyright sheriff',
+ 'CopyRightCheck',
+ 'Copyscape',
+ 'cortex\\/',
+ 'Cosmos4j\\.feedback',
+ 'Covario-IDS',
+ 'Craw\\/',
+ 'Crescent',
+ 'Criteo',
+ 'Crowsnest',
+ 'CSHttp',
+ 'CSSCheck',
+ 'Cula\\/',
+ 'curb',
+ 'Curious George',
+ 'curl',
+ 'cuwhois\\/',
+ 'cybo\\.com',
+ 'DAP\\/NetHTTP',
+ 'DareBoost',
+ 'DatabaseDriverMysqli',
+ 'DataCha0s',
+ 'Datafeedwatch',
+ 'Datanyze',
+ 'DataparkSearch',
+ 'dataprovider',
+ 'DataXu',
+ 'Daum(oa)?[ \\/][0-9]',
+ 'dBpoweramp',
+ 'ddline',
+ 'deeris',
+ 'delve\\.ai',
+ 'Demon',
+ 'DeuSu',
+ 'developers\\.google\\.com\\/\\+\\/web\\/snippet\\/',
+ 'Devil',
+ 'Digg',
+ 'Digincore',
+ 'DigitalPebble',
+ 'Dirbuster',
+ 'Discourse Forum Onebox',
+ 'Dispatch\\/',
+ 'Disqus\\/',
+ 'DittoSpyder',
+ 'dlvr',
+ 'DMBrowser',
+ 'DNSPod-reporting',
+ 'docoloc',
+ 'Dolphin http client',
+ 'DomainAppender',
+ 'DomainLabz',
+ 'Domains Project\\/',
+ 'Donuts Content Explorer',
+ 'dotMailer content retrieval',
+ 'dotSemantic',
+ 'downforeveryoneorjustme',
+ 'Download Wonder',
+ 'downnotifier',
+ 'DowntimeDetector',
+ 'Drip',
+ 'drupact',
+ 'Drupal \\(\\+http:\\/\\/drupal\\.org\\/\\)',
+ 'DTS Agent',
+ 'dubaiindex',
+ 'DuplexWeb-Google',
+ 'DynatraceSynthetic',
+ 'EARTHCOM',
+ 'Easy-Thumb',
+ 'EasyDL',
+ 'Ebingbong',
+ 'ec2linkfinder',
+ 'eCairn-Grabber',
+ 'eCatch',
+ 'ECCP',
+ 'eContext\\/',
+ 'Ecxi',
+ 'EirGrabber',
+ 'ElectricMonk',
+ 'elefent',
+ 'EMail Exractor',
+ 'EMail Wolf',
+ 'EmailWolf',
+ 'Embarcadero',
+ 'Embed PHP Library',
+ 'Embedly',
+ 'endo\\/',
+ 'europarchive\\.org',
+ 'evc-batch',
+ 'EventMachine HttpClient',
+ 'Everwall Link Expander',
+ 'Evidon',
+ 'Evrinid',
+ 'ExactSearch',
+ 'ExaleadCloudview',
+ 'Excel\\/',
+ 'exif',
+ 'ExoRank',
+ 'Exploratodo',
+ 'Express WebPictures',
+ 'Extreme Picture Finder',
+ 'EyeNetIE',
+ 'ezooms',
+ 'facebookexternalhit',
+ 'facebookexternalua',
+ 'facebookplatform',
+ 'fairshare',
+ 'Faraday v',
+ 'fasthttp',
+ 'Faveeo',
+ 'Favicon downloader',
+ 'faviconarchive',
+ 'faviconkit',
+ 'FavOrg',
+ 'Feed Wrangler',
+ 'Feedable\\/',
+ 'Feedbin',
+ 'FeedBooster',
+ 'FeedBucket',
+ 'FeedBunch\\/',
+ 'FeedBurner',
+ 'feeder',
+ 'Feedly',
+ 'FeedshowOnline',
+ 'Feedshow\\/',
+ 'Feedspot',
+ 'FeedViewer\\/',
+ 'Feedwind\\/',
+ 'FeedZcollector',
+ 'feeltiptop',
+ 'Fetch API',
+ 'Fetch\\/[0-9]',
+ 'Fever\\/[0-9]',
+ 'FHscan',
+ 'Fiery%20Feeds',
+ 'Filestack',
+ 'Fimap',
+ 'findlink',
+ 'findthatfile',
+ 'FlashGet',
+ 'FlipboardBrowserProxy',
+ 'FlipboardProxy',
+ 'FlipboardRSS',
+ 'Flock\\/',
+ 'Florienzh\\/',
+ 'fluffy',
+ 'Flunky',
+ 'flynxapp',
+ 'forensiq',
+ 'FoundSeoTool',
+ 'free thumbnails',
+ 'Freeuploader',
+ 'FreshRSS',
+ 'Funnelback',
+ 'Fuzz Faster U Fool',
+ 'G-i-g-a-b-o-t',
+ 'g00g1e\\.net',
+ 'ganarvisitas',
+ 'gdnplus\\.com',
+ 'geek-tools',
+ 'Genieo',
+ 'GentleSource',
+ 'GetCode',
+ 'Getintent',
+ 'GetLinkInfo',
+ 'getprismatic',
+ 'GetRight',
+ 'getroot',
+ 'GetURLInfo\\/',
+ 'GetWeb',
+ 'Geziyor',
+ 'Ghost Inspector',
+ 'GigablastOpenSource',
+ 'GIS-LABS',
+ 'github-camo',
+ 'GitHub-Hookshot',
+ 'github\\.com',
+ 'Go http package',
+ 'Go [\\d\\.]* package http',
+ 'Go!Zilla',
+ 'Go-Ahead-Got-It',
+ 'Go-http-client',
+ 'go-mtasts\\/',
+ 'gobyus',
+ 'Gofeed',
+ 'gofetch',
+ 'Goldfire Server',
+ 'GomezAgent',
+ 'gooblog',
+ 'Goodzer\\/',
+ 'Google AppsViewer',
+ 'Google Desktop',
+ 'Google favicon',
+ 'Google Keyword Suggestion',
+ 'Google Keyword Tool',
+ 'Google Page Speed Insights',
+ 'Google PP Default',
+ 'Google Search Console',
+ 'Google Web Preview',
+ 'Google-Ads-Creatives-Assistant',
+ 'Google-Ads-Overview',
+ 'Google-Adwords',
+ 'Google-Apps-Script',
+ 'Google-Calendar-Importer',
+ 'Google-HotelAdsVerifier',
+ 'Google-HTTP-Java-Client',
+ 'Google-Podcast',
+ 'Google-Publisher-Plugin',
+ 'Google-Read-Aloud',
+ 'Google-SearchByImage',
+ 'Google-Site-Verification',
+ 'Google-SMTP-STS',
+ 'Google-speakr',
+ 'Google-Structured-Data-Testing-Tool',
+ 'Google-Transparency-Report',
+ 'google-xrawler',
+ 'Google-Youtube-Links',
+ 'GoogleDocs',
+ 'GoogleHC\\/',
+ 'GoogleProber',
+ 'GoogleProducer',
+ 'GoogleSites',
+ 'Gookey',
+ 'GoSpotCheck',
+ 'gosquared-thumbnailer',
+ 'Gotit',
+ 'GoZilla',
+ 'grabify',
+ 'GrabNet',
+ 'Grafula',
+ 'Grammarly',
+ 'GrapeFX',
+ 'GreatNews',
+ 'Gregarius',
+ 'GRequests',
+ 'grokkit',
+ 'grouphigh',
+ 'grub-client',
+ 'gSOAP\\/',
+ 'GT::WWW',
+ 'GTmetrix',
+ 'GuzzleHttp',
+ 'gvfs\\/',
+ 'HAA(A)?RTLAND http client',
+ 'Haansoft',
+ 'hackney\\/',
+ 'Hadi Agent',
+ 'HappyApps-WebCheck',
+ 'Hardenize',
+ 'Hatena',
+ 'Havij',
+ 'HaxerMen',
+ 'HeadlessChrome',
+ 'HEADMasterSEO',
+ 'HeartRails_Capture',
+ 'help@dataminr\\.com',
+ 'heritrix',
+ 'Hexometer',
+ 'historious',
+ 'hkedcity',
+ 'hledejLevne\\.cz',
+ 'Hloader',
+ 'HMView',
+ 'Holmes',
+ 'HonesoSearchEngine',
+ 'HootSuite Image proxy',
+ 'Hootsuite-WebFeed',
+ 'hosterstats',
+ 'HostTracker',
+ 'ht:\\/\\/check',
+ 'htdig',
+ 'HTMLparser',
+ 'htmlyse',
+ 'HTTP Banner Detection',
+ 'http-get',
+ 'HTTP-Header-Abfrage',
+ 'http-kit',
+ 'http-request\\/',
+ 'HTTP-Tiny',
+ 'HTTP::Lite',
+ 'http:\\/\\/www.neomo.de\\/',
+ 'HttpComponents',
+ 'httphr',
+ 'HTTPie',
+ 'HTTPMon',
+ 'httpRequest',
+ 'httpscheck',
+ 'httpssites_power',
+ 'httpunit',
+ 'HttpUrlConnection',
+ 'http\\.rb\\/',
+ 'HTTP_Compression_Test',
+ 'http_get',
+ 'http_request2',
+ 'http_requester',
+ 'httrack',
+ 'huaweisymantec',
+ 'HubSpot ',
+ 'HubSpot-Link-Resolver',
+ 'Humanlinks',
+ 'i2kconnect\\/',
+ 'Iblog',
+ 'ichiro',
+ 'Id-search',
+ 'IdeelaborPlagiaat',
+ 'IDG Twitter Links Resolver',
+ 'IDwhois\\/',
+ 'Iframely',
+ 'igdeSpyder',
+ 'iGooglePortal',
+ 'IlTrovatore',
+ 'Image Fetch',
+ 'Image Sucker',
+ 'ImageEngine\\/',
+ 'ImageVisu\\/',
+ 'Imagga',
+ 'imagineeasy',
+ 'imgsizer',
+ 'InAGist',
+ 'inbound\\.li parser',
+ 'InDesign%20CC',
+ 'Indy Library',
+ 'InetURL',
+ 'infegy',
+ 'infohelfer',
+ 'InfoTekies',
+ 'InfoWizards Reciprocal Link',
+ 'inpwrd\\.com',
+ 'instabid',
+ 'Instapaper',
+ 'Integrity',
+ 'integromedb',
+ 'Intelliseek',
+ 'InterGET',
+ 'Internet Ninja',
+ 'InternetSeer',
+ 'internetVista monitor',
+ 'internetwache',
+ 'internet_archive',
+ 'intraVnews',
+ 'IODC',
+ 'IOI',
+ 'iplabel',
+ 'ips-agent',
+ 'IPS\\/[0-9]',
+ 'IPWorks HTTP\\/S Component',
+ 'iqdb\\/',
+ 'Iria',
+ 'Irokez',
+ 'isitup\\.org',
+ 'iskanie',
+ 'isUp\\.li',
+ 'iThemes Sync\\/',
+ 'IZaBEE',
+ 'iZSearch',
+ 'JAHHO',
+ 'janforman',
+ 'Jaunt\\/',
+ 'Java.*outbrain',
+ 'javelin\\.io',
+ 'Jbrofuzz',
+ 'Jersey\\/',
+ 'JetCar',
+ 'Jigsaw',
+ 'Jobboerse',
+ 'JobFeed discovery',
+ 'Jobg8 URL Monitor',
+ 'jobo',
+ 'Jobrapido',
+ 'Jobsearch1\\.5',
+ 'JoinVision Generic',
+ 'JolokiaPwn',
+ 'Joomla',
+ 'Jorgee',
+ 'JS-Kit',
+ 'JungleKeyThumbnail',
+ 'JustView',
+ 'Kaspersky Lab CFR link resolver',
+ 'Kelny\\/',
+ 'Kerrigan\\/',
+ 'KeyCDN',
+ 'Keyword Density',
+ 'Keywords Research',
+ 'khttp\\/',
+ 'KickFire',
+ 'KimonoLabs\\/',
+ 'Kml-Google',
+ 'knows\\.is',
+ 'KOCMOHABT',
+ 'kouio',
+ 'kube-probe',
+ 'kubectl',
+ 'kulturarw3',
+ 'KumKie',
+ 'Larbin',
+ 'Lavf\\/',
+ 'leakix\\.net',
+ 'LeechFTP',
+ 'LeechGet',
+ 'letsencrypt',
+ 'Lftp',
+ 'LibVLC',
+ 'LibWeb',
+ 'Libwhisker',
+ 'libwww',
+ 'Licorne',
+ 'Liferea\\/',
+ 'Lighthouse',
+ 'Lightspeedsystems',
+ 'Likse',
+ 'limber\\.io',
+ 'Link Valet',
+ 'LinkAlarm\\/',
+ 'LinkAnalyser',
+ 'linkCheck',
+ 'linkdex',
+ 'LinkExaminer',
+ 'linkfluence',
+ 'linkpeek',
+ 'LinkPreview',
+ 'LinkScan',
+ 'LinksManager',
+ 'LinkTiger',
+ 'LinkWalker',
+ 'link_thumbnailer',
+ 'Lipperhey',
+ 'Litemage_walker',
+ 'livedoor ScreenShot',
+ 'LoadImpactRload',
+ 'localsearch-web',
+ 'LongURL API',
+ 'longurl-r-package',
+ 'looid\\.com',
+ 'looksystems\\.net',
+ 'ltx71',
+ 'lua-resty-http',
+ 'Lucee \\(CFML Engine\\)',
+ 'Lush Http Client',
+ 'lwp-request',
+ 'lwp-trivial',
+ 'LWP::Simple',
+ 'lycos',
+ 'LYT\\.SR',
+ 'L\\.webis',
+ 'mabontland',
+ 'MacOutlook\\/',
+ 'Mag-Net',
+ 'MagpieRSS',
+ 'Mail::STS',
+ 'MailChimp',
+ 'Mail\\.Ru',
+ 'Majestic12',
+ 'makecontact\\/',
+ 'Mandrill',
+ 'MapperCmd',
+ 'marketinggrader',
+ 'MarkMonitor',
+ 'MarkWatch',
+ 'Mass Downloader',
+ 'masscan\\/',
+ 'Mata Hari',
+ 'mattermost',
+ 'Mediametric',
+ 'Mediapartners-Google',
+ 'mediawords',
+ 'MegaIndex\\.ru',
+ 'MeltwaterNews',
+ 'Melvil Rawi',
+ 'MemGator',
+ 'Metaspinner',
+ 'MetaURI',
+ 'MFC_Tear_Sample',
+ 'Microsearch',
+ 'Microsoft Data Access',
+ 'Microsoft Office',
+ 'Microsoft Outlook',
+ 'Microsoft Windows Network Diagnostics',
+ 'Microsoft-WebDAV-MiniRedir',
+ 'Microsoft\\.Data\\.Mashup',
+ 'MIDown tool',
+ 'MIIxpc',
+ 'Mindjet',
+ 'Miniature\\.io',
+ 'Miniflux',
+ 'mio_httpc',
+ 'Miro-HttpClient',
+ 'Mister PiX',
+ 'mixdata dot com',
+ 'mixed-content-scan',
+ 'mixnode',
+ 'Mnogosearch',
+ 'mogimogi',
+ 'Mojeek',
+ 'Mojolicious \\(Perl\\)',
+ 'monitis',
+ 'Monitority\\/',
+ 'Monit\\/',
+ 'montastic',
+ 'MonTools',
+ 'Moreover',
+ 'Morfeus Fucking Scanner',
+ 'Morning Paper',
+ 'MovableType',
+ 'mowser',
+ 'Mrcgiguy',
+ 'Mr\\.4x3 Powered',
+ 'MS Web Services Client Protocol',
+ 'MSFrontPage',
+ 'mShots',
+ 'MuckRack\\/',
+ 'muhstik-scan',
+ 'MVAClient',
+ 'MxToolbox\\/',
+ 'myseosnapshot',
+ 'nagios',
+ 'Najdi\\.si',
+ 'Name Intelligence',
+ 'NameFo\\.com',
+ 'Nameprotect',
+ 'nationalarchives',
+ 'Navroad',
+ 'NearSite',
+ 'Needle',
+ 'Nessus',
+ 'Net Vampire',
+ 'NetAnts',
+ 'NETCRAFT',
+ 'NetLyzer',
+ 'NetMechanic',
+ 'NetNewsWire',
+ 'Netpursual',
+ 'netresearch',
+ 'NetShelter ContentScan',
+ 'Netsparker',
+ 'NetSystemsResearch',
+ 'nettle',
+ 'NetTrack',
+ 'Netvibes',
+ 'NetZIP',
+ 'Neustar WPM',
+ 'NeutrinoAPI',
+ 'NewRelicPinger',
+ 'NewsBlur .*Finder',
+ 'NewsGator',
+ 'newsme',
+ 'newspaper\\/',
+ 'Nexgate Ruby Client',
+ 'NG-Search',
+ 'nghttp2',
+ 'Nibbler',
+ 'NICErsPRO',
+ 'NihilScio',
+ 'Nikto',
+ 'nineconnections',
+ 'NLNZ_IAHarvester',
+ 'Nmap Scripting Engine',
+ 'node-fetch',
+ 'node-superagent',
+ 'node-urllib',
+ 'Nodemeter',
+ 'NodePing',
+ 'node\\.io',
+ 'nominet\\.org\\.uk',
+ 'nominet\\.uk',
+ 'Norton-Safeweb',
+ 'Notifixious',
+ 'notifyninja',
+ 'NotionEmbedder',
+ 'nuhk',
+ 'nutch',
+ 'Nuzzel',
+ 'nWormFeedFinder',
+ 'nyawc\\/',
+ 'Nymesis',
+ 'NYU',
+ 'Observatory\\/',
+ 'Ocelli\\/',
+ 'Octopus',
+ 'oegp',
+ 'Offline Explorer',
+ 'Offline Navigator',
+ 'OgScrper',
+ 'okhttp',
+ 'omgili',
+ 'OMSC',
+ 'Online Domain Tools',
+ 'Open Source RSS',
+ 'OpenCalaisSemanticProxy',
+ 'Openfind',
+ 'OpenLinkProfiler',
+ 'Openstat\\/',
+ 'OpenVAS',
+ 'OPPO A33',
+ 'Optimizer',
+ 'Orbiter',
+ 'OrgProbe\\/',
+ 'orion-semantics',
+ 'Outlook-Express',
+ 'Outlook-iOS',
+ 'Owler',
+ 'Owlin',
+ 'ownCloud News',
+ 'ow\\.ly',
+ 'OxfordCloudService',
+ 'page scorer',
+ 'Page Valet',
+ 'page2rss',
+ 'PageFreezer',
+ 'PageGrabber',
+ 'PagePeeker',
+ 'PageScorer',
+ 'Pagespeed\\/',
+ 'PageThing',
+ 'page_verifier',
+ 'Panopta',
+ 'panscient',
+ 'Papa Foto',
+ 'parsijoo',
+ 'Pavuk',
+ 'PayPal IPN',
+ 'pcBrowser',
+ 'Pcore-HTTP',
+ 'PDF24 URL To PDF',
+ 'Pearltrees',
+ 'PECL::HTTP',
+ 'peerindex',
+ 'Peew',
+ 'PeoplePal',
+ 'Perlu -',
+ 'PhantomJS Screenshoter',
+ 'PhantomJS\\/',
+ 'Photon\\/',
+ 'php-requests',
+ 'phpservermon',
+ 'Pi-Monster',
+ 'Picscout',
+ 'Picsearch',
+ 'PictureFinder',
+ 'Pimonster',
+ 'Pingability',
+ 'PingAdmin\\.Ru',
+ 'Pingdom',
+ 'Pingoscope',
+ 'PingSpot',
+ 'ping\\.blo\\.gs',
+ 'pinterest\\.com',
+ 'Pixray',
+ 'Pizilla',
+ 'Plagger\\/',
+ 'Pleroma ',
+ 'Ploetz \\+ Zeller',
+ 'Plukkie',
+ 'plumanalytics',
+ 'PocketImageCache',
+ 'PocketParser',
+ 'Pockey',
+ 'PodcastAddict\\/',
+ 'POE-Component-Client-HTTP',
+ 'Polymail\\/',
+ 'Pompos',
+ 'Porkbun',
+ 'Port Monitor',
+ 'postano',
+ 'postfix-mta-sts-resolver',
+ 'PostmanRuntime',
+ 'postplanner\\.com',
+ 'PostPost',
+ 'postrank',
+ 'PowerPoint\\/',
+ 'Prebid',
+ 'Prerender',
+ 'Priceonomics Analysis Engine',
+ 'PrintFriendly',
+ 'PritTorrent',
+ 'Prlog',
+ 'probethenet',
+ 'Project ?25499',
+ 'Project-Resonance',
+ 'prospectb2b',
+ 'Protopage',
+ 'ProWebWalker',
+ 'proximic',
+ 'PRTG Network Monitor',
+ 'pshtt, https scanning',
+ 'PTST ',
+ 'PTST\\/[0-9]+',
+ 'Pump',
+ 'Python-httplib2',
+ 'python-httpx',
+ 'python-requests',
+ 'Python-urllib',
+ 'Qirina Hurdler',
+ 'QQDownload',
+ 'QrafterPro',
+ 'Qseero',
+ 'Qualidator',
+ 'QueryN Metasearch',
+ 'queuedriver',
+ 'quic-go-HTTP\\/',
+ 'QuiteRSS',
+ 'Quora Link Preview',
+ 'Qwantify',
+ 'Radian6',
+ 'RadioPublicImageResizer',
+ 'Railgun\\/',
+ 'RankActive',
+ 'RankFlex',
+ 'RankSonicSiteAuditor',
+ 'RapidLoad\\/',
+ 'Re-re Studio',
+ 'ReactorNetty',
+ 'Readability',
+ 'RealDownload',
+ 'RealPlayer%20Downloader',
+ 'RebelMouse',
+ 'Recorder',
+ 'RecurPost\\/',
+ 'redback\\/',
+ 'ReederForMac',
+ 'Reeder\\/',
+ 'ReGet',
+ 'RepoMonkey',
+ 'request\\.js',
+ 'reqwest\\/',
+ 'ResponseCodeTest',
+ 'RestSharp',
+ 'Riddler',
+ 'Rival IQ',
+ 'Robosourcer',
+ 'Robozilla',
+ 'ROI Hunter',
+ 'RPT-HTTPClient',
+ 'RSSMix\\/',
+ 'RSSOwl',
+ 'RyowlEngine',
+ 'safe-agent-scanner',
+ 'SalesIntelligent',
+ 'Saleslift',
+ 'SAP NetWeaver Application Server',
+ 'SauceNAO',
+ 'SBIder',
+ 'sc-downloader',
+ 'scalaj-http',
+ 'Scamadviser-Frontend',
+ 'ScanAlert',
+ 'scan\\.lol',
+ 'Scoop',
+ 'scooter',
+ 'ScopeContentAG-HTTP-Client',
+ 'ScoutJet',
+ 'ScoutURLMonitor',
+ 'ScrapeBox Page Scanner',
+ 'Scrapy',
+ 'Screaming',
+ 'ScreenShotService',
+ 'Scrubby',
+ 'Scrutiny\\/',
+ 'Search37',
+ 'searchenginepromotionhelp',
+ 'Searchestate',
+ 'SearchExpress',
+ 'SearchSight',
+ 'SearchWP',
+ 'search\\.thunderstone',
+ 'Seeker',
+ 'semanticdiscovery',
+ 'semanticjuice',
+ 'Semiocast HTTP client',
+ 'Semrush',
+ 'Sendsay\\.Ru',
+ 'sentry\\/',
+ 'SEO Browser',
+ 'Seo Servis',
+ 'seo-nastroj\\.cz',
+ 'seo4ajax',
+ 'Seobility',
+ 'SEOCentro',
+ 'SeoCheck',
+ 'SEOkicks',
+ 'SEOlizer',
+ 'Seomoz',
+ 'SEOprofiler',
+ 'seoscanners',
+ 'SEOsearch',
+ 'seositecheckup',
+ 'SEOstats',
+ 'servernfo',
+ 'sexsearcher',
+ 'Seznam',
+ 'Shelob',
+ 'Shodan',
+ 'Shoppimon',
+ 'ShopWiki',
+ 'ShortLinkTranslate',
+ 'shortURL lengthener',
+ 'shrinktheweb',
+ 'Sideqik',
+ 'Siege',
+ 'SimplePie',
+ 'SimplyFast',
+ 'Siphon',
+ 'SISTRIX',
+ 'Site Sucker',
+ 'Site-Shot\\/',
+ 'Site24x7',
+ 'SiteBar',
+ 'Sitebeam',
+ 'Sitebulb\\/',
+ 'SiteCondor',
+ 'SiteExplorer',
+ 'SiteGuardian',
+ 'Siteimprove',
+ 'SiteIndexed',
+ 'Sitemap(s)? Generator',
+ 'SitemapGenerator',
+ 'SiteMonitor',
+ 'Siteshooter B0t',
+ 'SiteSnagger',
+ 'SiteSucker',
+ 'SiteTruth',
+ 'Sitevigil',
+ 'sitexy\\.com',
+ 'SkypeUriPreview',
+ 'Slack\\/',
+ 'sli-systems\\.com',
+ 'slider\\.com',
+ 'slurp',
+ 'SlySearch',
+ 'SmartDownload',
+ 'SMRF URL Expander',
+ 'SMUrlExpander',
+ 'Snake',
+ 'Snappy',
+ 'SnapSearch',
+ 'Snarfer\\/',
+ 'SniffRSS',
+ 'sniptracker',
+ 'Snoopy',
+ 'SnowHaze Search',
+ 'sogou web',
+ 'SortSite',
+ 'Sottopop',
+ 'sovereign\\.ai',
+ 'SpaceBison',
+ 'SpamExperts',
+ 'Spammen',
+ 'Spanner',
+ 'spaziodati',
+ 'SPDYCheck',
+ 'Specificfeeds',
+ 'speedy',
+ 'SPEng',
+ 'Spinn3r',
+ 'spray-can',
+ 'Sprinklr ',
+ 'spyonweb',
+ 'sqlmap',
+ 'Sqlworm',
+ 'Sqworm',
+ 'SSL Labs',
+ 'ssl-tools',
+ 'StackRambler',
+ 'Statastico\\/',
+ 'Statically-',
+ 'StatusCake',
+ 'Steeler',
+ 'Stratagems Kumo',
+ 'Stripe\\/',
+ 'Stroke\\.cz',
+ 'StudioFACA',
+ 'StumbleUpon',
+ 'suchen',
+ 'Sucuri',
+ 'summify',
+ 'SuperHTTP',
+ 'Surphace Scout',
+ 'Suzuran',
+ 'swcd ',
+ 'Symfony BrowserKit',
+ 'Symfony2 BrowserKit',
+ 'Synapse\\/',
+ 'Syndirella\\/',
+ 'SynHttpClient-Built',
+ 'Sysomos',
+ 'sysscan',
+ 'Szukacz',
+ 'T0PHackTeam',
+ 'tAkeOut',
+ 'Tarantula\\/',
+ 'Taringa UGC',
+ 'TarmotGezgin',
+ 'tchelebi\\.io',
+ 'techiaith\\.cymru',
+ 'TelegramBot',
+ 'Teleport',
+ 'Telesoft',
+ 'Telesphoreo',
+ 'Telesphorep',
+ 'Tenon\\.io',
+ 'teoma',
+ 'terrainformatica',
+ 'Test Certificate Info',
+ 'testuri',
+ 'Tetrahedron',
+ 'TextRazor Downloader',
+ 'The Drop Reaper',
+ 'The Expert HTML Source Viewer',
+ 'The Intraformant',
+ 'The Knowledge AI',
+ 'theinternetrules',
+ 'TheNomad',
+ 'Thinklab',
+ 'Thumbor',
+ 'Thumbshots',
+ 'ThumbSniper',
+ 'timewe\\.net',
+ 'TinEye',
+ 'Tiny Tiny RSS',
+ 'TLSProbe\\/',
+ 'Toata',
+ 'topster',
+ 'touche\\.com',
+ 'Traackr\\.com',
+ 'tracemyfile',
+ 'Trackuity',
+ 'TrapitAgent',
+ 'Trendiction',
+ 'Trendsmap',
+ 'trendspottr',
+ 'truwoGPS',
+ 'TryJsoup',
+ 'TulipChain',
+ 'Turingos',
+ 'Turnitin',
+ 'tweetedtimes',
+ 'Tweetminster',
+ 'Tweezler\\/',
+ 'twibble',
+ 'Twice',
+ 'Twikle',
+ 'Twingly',
+ 'Twisted PageGetter',
+ 'Typhoeus',
+ 'ubermetrics-technologies',
+ 'uclassify',
+ 'UdmSearch',
+ 'ultimate_sitemap_parser',
+ 'unchaos',
+ 'unirest-java',
+ 'UniversalFeedParser',
+ 'unshortenit',
+ 'Unshorten\\.It',
+ 'Untiny',
+ 'UnwindFetchor',
+ 'updated',
+ 'updown\\.io daemon',
+ 'Upflow',
+ 'Uptimia',
+ 'URL Verifier',
+ 'Urlcheckr',
+ 'URLitor',
+ 'urlresolver',
+ 'Urlstat',
+ 'URLTester',
+ 'UrlTrends Ranking Updater',
+ 'URLy Warning',
+ 'URLy\\.Warning',
+ 'URL\\/Emacs',
+ 'Vacuum',
+ 'Vagabondo',
+ 'VB Project',
+ 'vBSEO',
+ 'VCI',
+ 'via ggpht\\.com GoogleImageProxy',
+ 'Virusdie',
+ 'visionutils',
+ 'vkShare',
+ 'VoidEYE',
+ 'Voil',
+ 'voltron',
+ 'voyager\\/',
+ 'VSAgent\\/',
+ 'VSB-TUO\\/',
+ 'Vulnbusters Meter',
+ 'VYU2',
+ 'w3af\\.org',
+ 'W3C-checklink',
+ 'W3C-mobileOK',
+ 'W3C_Unicorn',
+ 'WAC-OFU',
+ 'WakeletLinkExpander',
+ 'WallpapersHD',
+ 'Wallpapers\\/[0-9]+',
+ 'wangling',
+ 'Wappalyzer',
+ 'WatchMouse',
+ 'WbSrch\\/',
+ 'WDT\\.io',
+ 'Web Auto',
+ 'Web Collage',
+ 'Web Enhancer',
+ 'Web Fetch',
+ 'Web Fuck',
+ 'Web Pix',
+ 'Web Sauger',
+ 'Web spyder',
+ 'Web Sucker',
+ 'web-capture\\.net',
+ 'Web-sniffer',
+ 'Webalta',
+ 'Webauskunft',
+ 'WebAuto',
+ 'WebCapture',
+ 'WebClient\\/',
+ 'webcollage',
+ 'WebCookies',
+ 'WebCopier',
+ 'WebCorp',
+ 'WebDataStats',
+ 'WebDoc',
+ 'WebEnhancer',
+ 'WebFetch',
+ 'WebFuck',
+ 'WebGazer',
+ 'WebGo IS',
+ 'WebImageCollector',
+ 'WebImages',
+ 'WebIndex',
+ 'webkit2png',
+ 'WebLeacher',
+ 'webmastercoffee',
+ 'webmon ',
+ 'WebPix',
+ 'WebReaper',
+ 'WebSauger',
+ 'webscreenie',
+ 'Webshag',
+ 'Webshot',
+ 'Website Quester',
+ 'websitepulse agent',
+ 'WebsiteQuester',
+ 'Websnapr',
+ 'WebSniffer',
+ 'Webster',
+ 'WebStripper',
+ 'WebSucker',
+ 'webtech\\/',
+ 'WebThumbnail',
+ 'Webthumb\\/',
+ 'WebWhacker',
+ 'WebZIP',
+ 'WeLikeLinks',
+ 'WEPA',
+ 'WeSEE',
+ 'wf84',
+ 'Wfuzz\\/',
+ 'wget',
+ 'WhatCMS',
+ 'WhatsApp',
+ 'WhatsMyIP',
+ 'WhatWeb',
+ 'WhereGoes\\?',
+ 'Whibse',
+ 'WhoAPI\\/',
+ 'WhoRunsCoinHive',
+ 'Whynder Magnet',
+ 'Windows-RSS-Platform',
+ 'WinHttp-Autoproxy-Service',
+ 'WinHTTP\\/',
+ 'WinPodder',
+ 'wkhtmlto',
+ 'wmtips',
+ 'Woko',
+ 'Wolfram HTTPClient',
+ 'woorankreview',
+ 'WordPress\\/',
+ 'WordupinfoSearch',
+ 'Word\\/',
+ 'worldping-api',
+ 'wotbox',
+ 'WP Engine Install Performance API',
+ 'WP Rocket',
+ 'wpif',
+ 'wprecon\\.com survey',
+ 'WPScan',
+ 'wscheck',
+ 'Wtrace',
+ 'WWW-Collector-E',
+ 'WWW-Mechanize',
+ 'WWW::Document',
+ 'WWW::Mechanize',
+ 'WWWOFFLE',
+ 'www\\.monitor\\.us',
+ 'x09Mozilla',
+ 'x22Mozilla',
+ 'XaxisSemanticsClassifier',
+ 'XenForo\\/',
+ 'Xenu Link Sleuth',
+ 'XING-contenttabreceiver',
+ 'xpymep([0-9]?)\\.exe',
+ 'Y!J-[A-Z][A-Z][A-Z]',
+ 'Yaanb',
+ 'yacy',
+ 'Yahoo Link Preview',
+ 'YahooCacheSystem',
+ 'YahooMailProxy',
+ 'YahooYSMcm',
+ 'YandeG',
+ 'Yandex(?!Search)',
+ 'yanga',
+ 'yeti',
+ 'Yo-yo',
+ 'Yoleo Consumer',
+ 'yomins\\.com',
+ 'yoogliFetchAgent',
+ 'YottaaMonitor',
+ 'Your-Website-Sucks',
+ 'yourls\\.org',
+ 'YoYs\\.net',
+ 'YP\\.PL',
+ 'Zabbix',
+ 'Zade',
+ 'Zao',
+ 'Zauba',
+ 'Zemanta Aggregator',
+ 'Zend\\\\Http\\\\Client',
+ 'Zend_Http_Client',
+ 'Zermelo',
+ 'Zeus ',
+ 'zgrab',
+ 'ZnajdzFoto',
+ 'ZnHTTP',
+ 'Zombie\\.js',
+ 'Zoom\\.Mac',
+ 'ZoteroTranslationServer',
+ 'ZyBorg',
+ '[a-z0-9\\-_]*(bot|crawl|archiver|transcoder|spider|uptime|validator|fetcher|cron|checker|reader|extractor|monitoring|analyzer|scraper)',
+ ];
+ }
+ }
+
+ var crawlers = Crawlers$1;
+
+ const Provider$1 = provider;
+
+ class Exclusions$1 extends Provider$1 {
+ constructor() {
+ super();
+
+ this.data = [
+ 'Safari.[\\d\\.]*',
+ 'Firefox.[\\d\\.]*',
+ ' Chrome.[\\d\\.]*',
+ 'Chromium.[\\d\\.]*',
+ 'MSIE.[\\d\\.]',
+ 'Opera\\/[\\d\\.]*',
+ 'Mozilla.[\\d\\.]*',
+ 'AppleWebKit.[\\d\\.]*',
+ 'Trident.[\\d\\.]*',
+ 'Windows NT.[\\d\\.]*',
+ 'Android [\\d\\.]*',
+ 'Macintosh.',
+ 'Ubuntu',
+ 'Linux',
+ '[ ]Intel',
+ 'Mac OS X [\\d_]*',
+ '(like )?Gecko(.[\\d\\.]*)?',
+ 'KHTML,',
+ 'CriOS.[\\d\\.]*',
+ 'CPU iPhone OS ([0-9_])* like Mac OS X',
+ 'CPU OS ([0-9_])* like Mac OS X',
+ 'iPod',
+ 'compatible',
+ 'x86_..',
+ 'i686',
+ 'x64',
+ 'X11',
+ 'rv:[\\d\\.]*',
+ 'Version.[\\d\\.]*',
+ 'WOW64',
+ 'Win64',
+ 'Dalvik.[\\d\\.]*',
+ ' \\.NET CLR [\\d\\.]*',
+ 'Presto.[\\d\\.]*',
+ 'Media Center PC',
+ 'BlackBerry',
+ 'Build',
+ 'Opera Mini\\/\\d{1,2}\\.\\d{1,2}\\.[\\d\\.]*\\/\\d{1,2}\\.',
+ 'Opera',
+ ' \\.NET[\\d\\.]*',
+ 'cubot',
+ '; M bot',
+ '; CRONO',
+ '; B bot',
+ '; IDbot',
+ '; ID bot',
+ '; POWER BOT',
+ 'OCTOPUS-CORE',
+ ];
+ }
+ }
+
+ var exclusions = Exclusions$1;
+
+ const Provider = provider;
+
+ class Headers$1 extends Provider {
+ constructor() {
+ super();
+
+ this.data = [
+ 'USER-AGENT',
+ 'X-OPERAMINI-PHONE-UA',
+ 'X-DEVICE-USER-AGENT',
+ 'X-ORIGINAL-USER-AGENT',
+ 'X-SKYFIRE-PHONE',
+ 'X-BOLT-PHONE-UA',
+ 'DEVICE-STOCK-UA',
+ 'X-UCBROWSER-DEVICE-UA',
+ 'FROM',
+ 'X-SCANNER',
+ ];
+ }
+ }
+
+ var headers = Headers$1;
+
+ const Crawlers = crawlers;
+ const Exclusions = exclusions;
+ const Headers = headers;
+
+ class Crawler$1 {
+ constructor(request, headers, userAgent) {
+ /**
+ * Init classes
+ */
+ this._init();
+
+ /**
+ * This request must be an object
+ */
+ this.request = typeof request === 'object' ? request : {};
+
+ // The regex-list must not be used with g-flag!
+ // See: https://stackoverflow.com/questions/1520800/why-does-a-regexp-with-global-flag-give-wrong-results
+ this.compiledRegexList = this.compileRegex(this.crawlers.getAll(), 'i');
+
+ // The exclusions should be used with g-flag in order to remove each value.
+ this.compiledExclusions = this.compileRegex(this.exclusions.getAll(), 'gi');
+
+ /**
+ * Set http headers
+ */
+ this.setHttpHeaders(headers);
+
+ /**
+ * Set userAgent
+ */
+ this.userAgent = this.setUserAgent(userAgent);
+ }
+
+ /**
+ * Init Classes Instances
+ */
+ _init() {
+ this.crawlers = new Crawlers();
+ this.headers = new Headers();
+ this.exclusions = new Exclusions();
+ }
+
+ compileRegex(patterns, flags) {
+ return new RegExp(patterns.join('|'), flags);
+ }
+
+ /**
+ * Set HTTP headers.
+ */
+ setHttpHeaders(headers) {
+ // Use the Request headers if httpHeaders is not defined
+ if (typeof headers === 'undefined' || Object.keys(headers).length === 0) {
+ headers = Object.keys(this.request).length ? this.request.headers : {};
+ }
+
+ // Save the headers.
+ this.httpHeaders = headers;
+ }
+
+ /**
+ * Set user agent
+ */
+ setUserAgent(userAgent) {
+ if (
+ typeof userAgent === 'undefined' ||
+ userAgent === null ||
+ !userAgent.length
+ ) {
+ for (const header of this.getUaHttpHeaders()) {
+ if (Object.keys(this.httpHeaders).indexOf(header.toLowerCase()) >= 0) {
+ userAgent += this.httpHeaders[header.toLowerCase()] + ' ';
+ }
+ }
+ }
+
+ return userAgent;
+ }
+
+ /**
+ * Get user agent headers
+ */
+ getUaHttpHeaders() {
+ return this.headers.getAll();
+ }
+
+ /**
+ * Check user agent string against the regex.
+ */
+ isCrawler(userAgent = undefined) {
+ if (Buffer.byteLength(userAgent || '', 'utf8') > 4096) {
+ return false;
+ }
+
+ var agent =
+ typeof userAgent === 'undefined' || userAgent === null
+ ? this.userAgent
+ : userAgent;
+
+ // test on compiled regx
+ agent = agent.replace(this.compiledExclusions, '');
+
+ if (agent.trim().length === 0) {
+ return false;
+ }
+
+ var matches = this.compiledRegexList.exec(agent);
+
+ if (matches) {
+ this.matches = matches;
+ }
+
+ return matches !== null ? (matches.length ? true : false) : false;
+ }
+
+ /**
+ * Return the matches.
+ */
+ getMatches() {
+ return this.matches !== undefined
+ ? this.matches.length
+ ? this.matches[0]
+ : null
+ : {};
+ }
+ }
+
+ var crawler = Crawler$1;
+
+ const Crawler = crawler;
+
+ var src = {
+ Crawler,
+ middleware(cb) {
+ return (req, res, next) => {
+ // If there is a cb, execute it
+ if (typeof cb === 'function') {
+ cb.call(req, res);
+ }
+ // Initiate
+ req.Crawler = new Crawler(req);
+ next();
+ };
+ },
+ };
+
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new [...]
+
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
+
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
+ var newTracker = function newTracker() {
+ var wem = {
+ enableWem: function enableWem() {
+ wem._enableWem(true);
+ },
+ disableWem: function disableWem() {
+ wem._enableWem(false);
+ },
+
+ /**
+ * This function initialize the context in the page it is called internally and should not be called twice in the same page
+ *
+ * @param {object} digitalData config of the tracker
+ */
+ init: function init(digitalData) {
+ // added for external tracker
+ // store digitalData in tracker instance instead of window.
+ wem.digitalData = digitalData; // new conf:
+
+ wem.trackerProfileIdCookieName = wem.digitalData.wemInitConfig.trackerProfileIdCookieName ? wem.digitalData.wemInitConfig.trackerProfileIdCookieName : "wem-profile-id";
+ wem.trackerSessionIdCookieName = wem.digitalData.wemInitConfig.trackerSessionIdCookieName ? wem.digitalData.wemInitConfig.trackerSessionIdCookieName : "wem-session-id";
+ wem.activateWem = wem.digitalData.wemInitConfig.activateWem;
+ var _wem$digitalData$wemI = wem.digitalData.wemInitConfig,
+ contextServerUrl = _wem$digitalData$wemI.contextServerUrl,
+ isPreview = _wem$digitalData$wemI.isPreview,
+ timeoutInMilliseconds = _wem$digitalData$wemI.timeoutInMilliseconds,
+ contextServerCookieName = _wem$digitalData$wemI.contextServerCookieName;
+ wem.contextServerCookieName = contextServerCookieName;
+ wem.contextServerUrl = contextServerUrl;
+ wem.timeoutInMilliseconds = timeoutInMilliseconds;
+ wem.formNamesToWatch = [];
+ wem.eventsPrevented = [];
+ wem.sessionID = wem.getCookie(wem.trackerSessionIdCookieName);
+ wem.fallback = false;
+
+ if (wem.sessionID === null) {
+ console.warn('[WEM] sessionID is null !');
+ } else if (!wem.sessionID || wem.sessionID === '') {
+ console.warn('[WEM] empty sessionID, setting to null !');
+ wem.sessionID = null;
+ }
+
+ if (isPreview) {
+ // do not execute fallback for preview!
+ return;
+ }
+
+ var cookieDisabled = !navigator.cookieEnabled;
+ var noSessionID = !wem.sessionID || wem.sessionID === '';
+ var crawlerDetected = navigator.userAgent;
+
+ if (crawlerDetected) {
+ var browserDetector = new src.Crawler();
+ crawlerDetected = browserDetector.isCrawler(navigator.userAgent);
+ }
+
+ if (cookieDisabled || noSessionID || crawlerDetected) {
+ document.addEventListener('DOMContentLoaded', function () {
+ wem._executeFallback('navigator cookie disabled: ' + cookieDisabled + ', no sessionID: ' + noSessionID + ', web crawler detected: ' + crawlerDetected);
+ });
+ return;
+ }
+
+ wem._registerCallback(function () {
+ if (wem.cxs.profileId) {
+ wem.setCookie(wem.trackerProfileIdCookieName, wem.cxs.profileId);
+ }
+
+ if (!wem.cxs.profileId) {
+ wem.removeCookie(wem.trackerProfileIdCookieName);
+ } // process tracked events
+
+
+ var videoNamesToWatch = [];
+ var clickToWatch = [];
+
+ if (wem.cxs.trackedConditions && wem.cxs.trackedConditions.length > 0) {
+ for (var i = 0; i < wem.cxs.trackedConditions.length; i++) {
+ switch (wem.cxs.trackedConditions[i].type) {
+ case 'formEventCondition':
+ if (wem.cxs.trackedConditions[i].parameterValues && wem.cxs.trackedConditions[i].parameterValues.formId) {
+ wem.formNamesToWatch.push(wem.cxs.trackedConditions[i].parameterValues.formId);
+ }
+
+ break;
+
+ case 'videoViewEventCondition':
+ if (wem.cxs.trackedConditions[i].parameterValues && wem.cxs.trackedConditions[i].parameterValues.videoId) {
+ videoNamesToWatch.push(wem.cxs.trackedConditions[i].parameterValues.videoId);
+ }
+
+ break;
+
+ case 'clickOnLinkEventCondition':
+ if (wem.cxs.trackedConditions[i].parameterValues && wem.cxs.trackedConditions[i].parameterValues.itemId) {
+ clickToWatch.push(wem.cxs.trackedConditions[i].parameterValues.itemId);
+ }
+
+ break;
+ }
+ }
+ }
+
+ var forms = document.querySelectorAll('form');
+
+ for (var formIndex = 0; formIndex < forms.length; formIndex++) {
+ var form = forms.item(formIndex);
+ var formName = form.getAttribute('name') ? form.getAttribute('name') : form.getAttribute('id'); // test attribute data-form-id to not add a listener on FF form
+
+ if (formName && wem.formNamesToWatch.indexOf(formName) > -1 && form.getAttribute('data-form-id') == null) {
+ // add submit listener on form that we need to watch only
+ console.info('[WEM] watching form ' + formName);
+ form.addEventListener('submit', wem._formSubmitEventListener, true);
+ }
+ }
+
+ for (var videoIndex = 0; videoIndex < videoNamesToWatch.length; videoIndex++) {
+ var videoName = videoNamesToWatch[videoIndex];
+ var video = document.getElementById(videoName) || document.getElementById(wem._resolveId(videoName));
+
+ if (video) {
+ video.addEventListener('play', wem.sendVideoEvent);
+ video.addEventListener('ended', wem.sendVideoEvent);
+ console.info('[WEM] watching video ' + videoName);
+ } else {
+ console.warn('[WEM] unable to watch video ' + videoName + ', video not found in the page');
+ }
+ }
+
+ for (var clickIndex = 0; clickIndex < clickToWatch.length; clickIndex++) {
+ var clickIdName = clickToWatch[clickIndex];
+ var click = document.getElementById(clickIdName) || document.getElementById(wem._resolveId(clickIdName)) ? document.getElementById(clickIdName) || document.getElementById(wem._resolveId(clickIdName)) : document.getElementsByName(clickIdName)[0];
+
+ if (click) {
+ click.addEventListener('click', wem.sendClickEvent);
+ console.info('[WEM] watching click ' + clickIdName);
+ } else {
+ console.warn('[WEM] unable to watch click ' + clickIdName + ', element not found in the page');
+ }
+ }
+ }); // Load the context once document is ready
+
+
+ document.addEventListener('DOMContentLoaded', function () {
+ wem.DOMLoaded = true; // complete already registered events
+
+ wem._checkUncompleteRegisteredEvents(); // Dispatch javascript events for the experience (perso/opti displayed from SSR, based on unomi events)
+
+
+ wem._dispatchJSExperienceDisplayedEvents(); // Some event may not need to be send to unomi, check for them and filter them out.
+
+
+ wem._filterUnomiEvents(); // Add referrer info into digitalData.page object.
+
+
+ wem._processReferrer(); // Build view event
+
+
+ var viewEvent = wem.buildEvent('view', wem.buildTargetPage(), wem.buildSource(wem.digitalData.site.siteInfo.siteID, 'site'));
+ viewEvent.flattenedProperties = {}; // Add URLParameters
+
+ if (location.search) {
+ viewEvent.flattenedProperties['URLParameters'] = wem.convertUrlParametersToObj(location.search);
+ } // Add interests
+
+
+ if (wem.digitalData.interests) {
+ viewEvent.flattenedProperties['interests'] = wem.digitalData.interests;
+ } // Register the page view event, it's unshift because it should be the first event, this is just for logical purpose. (page view comes before perso displayed event for example)
+
+
+ wem._registerEvent(viewEvent, true);
+
+ if (wem.activateWem) {
+ wem.loadContext();
+ } else {
+ wem._executeFallback('wem is not activated on current page');
+ }
+ });
+ },
+ convertUrlParametersToObj: function convertUrlParametersToObj(searchString) {
+ if (!searchString) {
+ return null;
+ }
+
+ return searchString.replace(/^\?/, '') // Only trim off a single leading interrobang.
+ .split('&').reduce(function (result, next) {
+ if (next === '') {
+ return result;
+ }
+
+ var pair = next.split('=');
+ var key = decodeURIComponent(pair[0]);
+ var value = typeof pair[1] !== 'undefined' && decodeURIComponent(pair[1]) || undefined;
+
+ if (Object.prototype.hasOwnProperty.call(result, key)) {
+ // Check to see if this property has been met before.
+ if (Array.isArray(result[key])) {
+ // Is it already an array?
+ result[key].push(value);
+ } else {
+ // Make it an array.
+ result[key] = [result[key], value];
+ }
+ } else {
+ // First time seen, just add it.
+ result[key] = value;
+ }
+
+ return result;
+ }, {});
+ },
+
+ /**
+ * This function will register a personalization
+ *
+ * @param {object} personalization
+ * @param {object} variants
+ * @param {boolean} [ajax] Deprecated: Ajax rendering is not supported anymore
+ * @param {function} [resultCallback]
+ */
+ registerPersonalizationObject: function registerPersonalizationObject(personalization, variants, ajax, resultCallback) {
+ var target = personalization.id;
+
+ wem._registerPersonalizationCallback(personalization, function (result) {
+ var successfulFilters = [];
+
+ for (var i = 0; i < result.length; i++) {
+ successfulFilters.push(variants[result[i]]);
+ }
+
+ var selectedFilter = null;
+
+ if (successfulFilters.length > 0) {
+ selectedFilter = successfulFilters[0];
+ var minPos = successfulFilters[0].position;
+
+ if (minPos >= 0) {
+ for (var j = 1; j < successfulFilters.length; j++) {
+ if (successfulFilters[j].position < minPos) {
+ selectedFilter = successfulFilters[j];
+ }
+ }
+ }
+ }
+
+ if (resultCallback) {
+ // execute callback
+ resultCallback(successfulFilters, selectedFilter);
+ } else {
+ if (selectedFilter) {
+ var targetFilters = document.getElementById(target).children;
+
+ for (var fIndex in targetFilters) {
+ var filter = targetFilters.item(fIndex);
+
+ if (filter) {
+ filter.style.display = filter.id === selectedFilter.content ? '' : 'none';
+ }
+ } // we now add control group information to event if the user is in the control group.
+
+
+ if (wem._isInControlGroup(target)) {
+ console.info('[WEM] Profile is in control group for target: ' + target + ', adding to personalization event...');
+ selectedFilter.event.target.properties.inControlGroup = true;
+
+ if (selectedFilter.event.target.properties.variants) {
+ selectedFilter.event.target.properties.variants.forEach(function (variant) {
+ return variant.inControlGroup = true;
+ });
+ }
+ } // send event to unomi
+
+
+ wem.collectEvent(wem._completeEvent(selectedFilter.event), function () {
+ console.info('[WEM] Personalization event successfully collected.');
+ }, function () {
+ console.error('[WEM] Could not send personalization event.');
+ }); //Trigger variant display event for personalization
+
+ wem._dispatchJSExperienceDisplayedEvent(selectedFilter.event);
+ } else {
+ var elements = document.getElementById(target).children;
+
+ for (var eIndex in elements) {
+ var el = elements.item(eIndex);
+ el.style.display = 'none';
+ }
+ }
+ }
+ });
+ },
+
+ /**
+ * This function will register an optimization test or A/B test
+ *
+ * @param {string} optimizationTestNodeId
+ * @param {string} goalId
+ * @param {string} containerId
+ * @param {object} variants
+ * @param {boolean} [ajax] Deprecated: Ajax rendering is not supported anymore
+ * @param {object} [variantsTraffic]
+ */
+ registerOptimizationTest: function registerOptimizationTest(optimizationTestNodeId, goalId, containerId, variants, ajax, variantsTraffic) {
+ // check persona panel forced variant
+ var selectedVariantId = wem.getUrlParameter('wemSelectedVariantId-' + optimizationTestNodeId); // check already resolved variant stored in local
+
+ if (selectedVariantId === null) {
+ if (wem.storageAvailable('sessionStorage')) {
+ selectedVariantId = sessionStorage.getItem(optimizationTestNodeId);
+ } else {
+ selectedVariantId = wem.getCookie('selectedVariantId');
+
+ if (selectedVariantId != null && selectedVariantId === '') {
+ selectedVariantId = null;
+ }
+ }
+ } // select random variant and call unomi
+
+
+ if (!(selectedVariantId && variants[selectedVariantId])) {
+ var keys = Object.keys(variants);
+
+ if (variantsTraffic) {
+ var rand = 100 * Math.random() << 0;
+
+ for (var nodeIdentifier in variantsTraffic) {
+ if ((rand -= variantsTraffic[nodeIdentifier]) < 0 && selectedVariantId == null) {
+ selectedVariantId = nodeIdentifier;
+ }
+ }
+ } else {
+ selectedVariantId = keys[keys.length * Math.random() << 0];
+ }
+
+ if (wem.storageAvailable('sessionStorage')) {
+ sessionStorage.setItem(optimizationTestNodeId, selectedVariantId);
+ } else {
+ wem.setCookie('selectedVariantId', selectedVariantId, 1);
+ } // spread event to unomi
+
+
+ wem._registerEvent(wem._completeEvent(variants[selectedVariantId].event));
+ } //Trigger variant display event for optimization
+ // (Wrapped in DOMContentLoaded because opti are resulted synchronously at page load, so we dispatch the JS even after page load, to be sure that listeners are ready)
+
+
+ window.addEventListener('DOMContentLoaded', function () {
+ wem._dispatchJSExperienceDisplayedEvent(variants[selectedVariantId].event);
+ });
+
+ if (selectedVariantId) {
+ // update persona panel selected variant
+ if (window.optimizedContentAreas && window.optimizedContentAreas[optimizationTestNodeId]) {
+ window.optimizedContentAreas[optimizationTestNodeId].selectedVariant = selectedVariantId;
+ } // display the good variant
+
+
+ document.getElementById(variants[selectedVariantId].content).style.display = '';
+ }
+ },
+
+ /**
+ * This function is used to load the current context in the page
+ *
+ * @param {boolean} [skipEvents=false] Should we send the events
+ * @param {boolean} [invalidate=false] Should we invalidate the current context
+ */
+ loadContext: function loadContext(skipEvents, invalidate) {
+ if (wem.contextLoaded) {
+ console.log('Context already requested by', wem.contextLoaded);
+ return;
+ }
+
+ var jsonData = {
+ requiredProfileProperties: wem.digitalData.wemInitConfig.requiredProfileProperties,
+ requiredSessionProperties: wem.digitalData.wemInitConfig.requiredSessionProperties,
+ requireSegments: wem.digitalData.wemInitConfig.requireSegments,
+ requireScores: wem.digitalData.wemInitConfig.requireScores,
+ source: wem.buildSourcePage()
+ };
+
+ if (!skipEvents) {
+ jsonData.events = wem.digitalData.events;
+ }
+
+ if (wem.digitalData.personalizationCallback) {
+ jsonData.personalizations = wem.digitalData.personalizationCallback.map(function (x) {
+ return x.personalization;
+ });
+ }
+
+ jsonData.sessionId = wem.sessionID;
+ var contextUrl = wem.contextServerUrl + '/context.json';
+
+ if (invalidate) {
+ contextUrl += '?invalidateSession=true&invalidateProfile=true';
+ }
+
+ wem.ajax({
+ url: contextUrl,
+ type: 'POST',
+ async: true,
+ contentType: 'text/plain;charset=UTF-8',
+ // Use text/plain to avoid CORS preflight
+ jsonData: jsonData,
+ dataType: 'application/json',
+ invalidate: invalidate,
+ success: wem._onSuccess,
+ error: function error() {
+ wem._executeFallback('error during context loading');
+ }
+ });
+ wem.contextLoaded = Error().stack;
+ console.info('[WEM] context loading...');
+ },
+
+ /**
+ * This function will send an event to Apache Unomi
+ * @param {object} event The event object to send, you can build it using wem.buildEvent(eventType, target, source)
+ * @param {function} successCallback will be executed in case of success
+ * @param {function} errorCallback will be executed in case of error
+ */
+ collectEvent: function collectEvent(event, successCallback, errorCallback) {
+ wem.collectEvents({
+ events: [event]
+ }, successCallback, errorCallback);
+ },
+
+ /**
+ * This function will send the events to Apache Unomi
+ *
+ * @param {object} events Javascript object { events: [event1, event2] }
+ * @param {function} successCallback will be executed in case of success
+ * @param {function} errorCallback will be executed in case of error
+ */
+ collectEvents: function collectEvents(events, successCallback, errorCallback) {
+ if (wem.fallback) {
+ // in case of fallback we dont want to collect any events
+ return;
+ }
+
+ events.sessionId = wem.sessionID ? wem.sessionID : '';
+ var data = JSON.stringify(events);
+ wem.ajax({
+ url: wem.contextServerUrl + '/eventcollector',
+ type: 'POST',
+ async: true,
+ contentType: 'text/plain;charset=UTF-8',
+ // Use text/plain to avoid CORS preflight
+ data: data,
+ dataType: 'application/json',
+ success: successCallback,
+ error: errorCallback
+ });
+ },
+
+ /**
+ * This function will build an event of type click and send it to Apache Unomi
+ *
+ * @param {object} event javascript
+ * @param {function} [successCallback] will be executed if case of success
+ * @param {function} [errorCallback] will be executed if case of error
+ */
+ sendClickEvent: function sendClickEvent(event, successCallback, errorCallback) {
+ if (event.target.id || event.target.name) {
+ console.info('[WEM] Send click event');
+ var targetId = event.target.id ? event.target.id : event.target.name;
+ var clickEvent = wem.buildEvent('click', wem.buildTarget(targetId, event.target.localName), wem.buildSourcePage());
+ var eventIndex = wem.eventsPrevented.indexOf(targetId);
+
+ if (eventIndex !== -1) {
+ wem.eventsPrevented.splice(eventIndex, 0);
+ } else {
+ wem.eventsPrevented.push(targetId);
+ event.preventDefault();
+ var target = event.target;
+ wem.collectEvent(clickEvent, function (xhr) {
+ console.info('[WEM] Click event successfully collected.');
+
+ if (successCallback) {
+ successCallback(xhr);
+ } else {
+ target.click();
+ }
+ }, function (xhr) {
+ console.error('[WEM] Could not send click event.');
+
+ if (errorCallback) {
+ errorCallback(xhr);
+ } else {
+ target.click();
+ }
+ });
+ }
+ }
+ },
+
+ /**
+ * This function will build an event of type video and send it to Apache Unomi
+ *
+ * @param {object} event javascript
+ * @param {function} [successCallback] will be executed if case of success
+ * @param {function} [errorCallback] will be executed if case of error
+ */
+ sendVideoEvent: function sendVideoEvent(event, successCallback, errorCallback) {
+ console.info('[WEM] catching video event');
+ var videoEvent = wem.buildEvent('video', wem.buildTarget(event.target.id, 'video', {
+ action: event.type
+ }), wem.buildSourcePage());
+ wem.collectEvent(videoEvent, function (xhr) {
+ console.info('[WEM] Video event successfully collected.');
+
+ if (successCallback) {
+ successCallback(xhr);
+ }
+ }, function (xhr) {
+ console.error('[WEM] Could not send video event.');
+
+ if (errorCallback) {
+ errorCallback(xhr);
+ }
+ });
+ },
+
+ /**
+ * This function return the basic structure for an event, it must be adapted to your need
+ *
+ * @param {string} eventType The name of your event
+ * @param {object} [target] The target object for your event can be build with wem.buildTarget(targetId, targetType, targetProperties)
+ * @param {object} [source] The source object for your event can be build with wem.buildSource(sourceId, sourceType, sourceProperties)
+ * @returns {{eventType: *, scope}}
+ */
+ buildEvent: function buildEvent(eventType, target, source) {
+ var event = {
+ eventType: eventType,
+ scope: wem.digitalData.scope
+ };
+
+ if (target) {
+ event.target = target;
+ }
+
+ if (source) {
+ event.source = source;
+ }
+
+ return event;
+ },
+
+ /**
+ * This function return an event of type form
+ *
+ * @param {string} formName The HTML name of id of the form to use in the target of the event
+ * @returns {*|{eventType: *, scope, source: {scope, itemId: string, itemType: string, properties: {}}, target: {scope, itemId: string, itemType: string, properties: {}}}}
+ */
+ buildFormEvent: function buildFormEvent(formName) {
+ return wem.buildEvent('form', wem.buildTarget(formName, 'form'), wem.buildSourcePage());
+ },
+
+ /**
+ * This function return the source object for a source of type page
+ *
+ * @returns {*|{scope, itemId: *, itemType: *}}
+ */
+ buildTargetPage: function buildTargetPage() {
+ return wem.buildTarget(wem.digitalData.page.pageInfo.pageID, 'page', wem.digitalData.page);
+ },
+
+ /**
+ * This function return the source object for a source of type page
+ *
+ * @returns {*|{scope, itemId: *, itemType: *}}
+ */
+ buildSourcePage: function buildSourcePage() {
+ return wem.buildSource(wem.digitalData.page.pageInfo.pageID, 'page', wem.digitalData.page);
+ },
+
+ /**
+ * This function return the basic structure for the target of your event
+ *
+ * @param {string} targetId The ID of the target
+ * @param {string} targetType The type of the target
+ * @param {object} [targetProperties] The optional properties of the target
+ * @returns {{scope, itemId: *, itemType: *}}
+ */
+ buildTarget: function buildTarget(targetId, targetType, targetProperties) {
+ return wem._buildObject(targetId, targetType, targetProperties);
+ },
+
+ /**
+ * This function return the basic structure for the source of your event
+ *
+ * @param {string} sourceId The ID of the source
+ * @param {string} sourceType The type of the source
+ * @param {object} [sourceProperties] The optional properties of the source
+ * @returns {{scope, itemId: *, itemType: *}}
+ */
+ buildSource: function buildSource(sourceId, sourceType, sourceProperties) {
+ return wem._buildObject(sourceId, sourceType, sourceProperties);
+ },
+
+ /*************************************/
+
+ /* Utility functions under this line */
+
+ /*************************************/
+
+ /**
+ * This is an utility function to set a cookie
+ *
+ * @param {string} cookieName name of the cookie
+ * @param {string} cookieValue value of the cookie
+ * @param {number} [expireDays] number of days to set the expire date
+ */
+ setCookie: function setCookie(cookieName, cookieValue, expireDays) {
+ var expires = '';
+
+ if (expireDays) {
+ var d = new Date();
+ d.setTime(d.getTime() + expireDays * 24 * 60 * 60 * 1000);
+ expires = '; expires=' + d.toUTCString();
+ }
+
+ document.cookie = cookieName + '=' + cookieValue + expires + '; path=/; SameSite=Strict';
+ },
+
+ /**
+ * This is an utility function to get a cookie
+ *
+ * @param {string} cookieName name of the cookie to get
+ * @returns {*} the value of the first cookie with the corresponding name or null if not found
+ */
+ getCookie: function getCookie(cookieName) {
+ var name = cookieName + '=';
+ var ca = document.cookie.split(';');
+
+ for (var i = 0; i < ca.length; i++) {
+ var c = ca[i];
+
+ while (c.charAt(0) == ' ') {
+ c = c.substring(1);
+ }
+
+ if (c.indexOf(name) == 0) {
+ return c.substring(name.length, c.length);
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * This is an utility function to remove a cookie
+ *
+ * @param {string} cookieName the name of the cookie to rename
+ */
+ removeCookie: function removeCookie(cookieName) {
+
+ wem.setCookie(cookieName, '', -1);
+ },
+
+ /**
+ * This is an utility function to execute AJAX call
+ *
+ * @param {object} options
+ */
+ ajax: function ajax(options) {
+ var xhr = new XMLHttpRequest();
+
+ if ('withCredentials' in xhr) {
+ xhr.open(options.type, options.url, options.async);
+ xhr.withCredentials = true;
+ } else if (typeof XDomainRequest != 'undefined') {
+ /* global XDomainRequest */
+ xhr = new XDomainRequest();
+ xhr.open(options.type, options.url);
+ }
+
+ if (options.contentType) {
+ xhr.setRequestHeader('Content-Type', options.contentType);
+ }
+
+ if (options.dataType) {
+ xhr.setRequestHeader('Accept', options.dataType);
+ }
+
+ if (options.responseType) {
+ xhr.responseType = options.responseType;
+ }
+
+ var requestExecuted = false;
+
+ if (wem.timeoutInMilliseconds !== -1) {
+ setTimeout(function () {
+ if (!requestExecuted) {
+ console.error('[WEM] XML request timeout, url: ' + options.url);
+ requestExecuted = true;
+
+ if (options.error) {
+ options.error(xhr);
+ }
+ }
+ }, wem.timeoutInMilliseconds);
+ }
+
+ xhr.onreadystatechange = function () {
+ if (!requestExecuted) {
+ if (xhr.readyState === 4) {
+ if (xhr.status === 200 || xhr.status === 204 || xhr.status === 304) {
+ if (xhr.responseText != null) {
+ requestExecuted = true;
+
+ if (options.success) {
+ options.success(xhr);
+ }
+ }
+ } else {
+ requestExecuted = true;
+
+ if (options.error) {
+ options.error(xhr);
+ }
+
+ console.error('[WEM] XML request error: ' + xhr.statusText + ' (' + xhr.status + ')');
+ }
+ }
+ }
+ };
+
+ if (options.jsonData) {
+ xhr.send(JSON.stringify(options.jsonData));
+ } else if (options.data) {
+ xhr.send(options.data);
+ } else {
+ xhr.send();
+ }
+ },
+
+ /**
+ * This is an utility function to check if the local storage is available or not
+ * @param type
+ * @returns {boolean}
+ */
+ storageAvailable: function storageAvailable(type) {
+ try {
+ var storage = window[type],
+ x = '__storage_test__';
+ storage.setItem(x, x);
+ storage.removeItem(x);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ },
+ dispatchJSEvent: function dispatchJSEvent(name, canBubble, cancelable, detail) {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent(name, canBubble, cancelable, detail);
+ document.dispatchEvent(event);
+ },
+
+ /**
+ * This is an utility function to get current url parameter value
+ * @param name, the name of the parameter
+ * @returns {string}
+ */
+ getUrlParameter: function getUrlParameter(name) {
+ name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
+ var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
+ var results = regex.exec(window.location.search);
+ return results === null ? null : decodeURIComponent(results[1].replace(/\+/g, ' '));
+ },
+
+ /*************************************/
+
+ /* Private functions under this line */
+
+ /*************************************/
+ _checkUncompleteRegisteredEvents: function _checkUncompleteRegisteredEvents() {
+ if (wem.digitalData && wem.digitalData.events) {
+ var _iterator = _createForOfIteratorHelper(wem.digitalData.events),
+ _step;
+
+ try {
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
+ var event = _step.value;
+
+ wem._completeEvent(event);
+ }
+ } catch (err) {
+ _iterator.e(err);
+ } finally {
+ _iterator.f();
+ }
+ }
+ },
+ _dispatchJSExperienceDisplayedEvents: function _dispatchJSExperienceDisplayedEvents() {
+ if (wem.digitalData && wem.digitalData.events) {
+ var _iterator2 = _createForOfIteratorHelper(wem.digitalData.events),
+ _step2;
+
+ try {
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
+ var event = _step2.value;
+
+ if (event.eventType === 'optimizationTestEvent' || event.eventType === 'personalizationEvent') {
+ wem._dispatchJSExperienceDisplayedEvent(event);
+ }
+ }
+ } catch (err) {
+ _iterator2.e(err);
+ } finally {
+ _iterator2.f();
+ }
+ }
+ },
+ _dispatchJSExperienceDisplayedEvent: function _dispatchJSExperienceDisplayedEvent(experienceUnomiEvent) {
+ if (!wem.fallback && experienceUnomiEvent && experienceUnomiEvent.target && experienceUnomiEvent.target.properties && experienceUnomiEvent.target.properties.variants && experienceUnomiEvent.target.properties.variants.length > 0) {
+ var typeMapper = {
+ optimizationTestEvent: 'optimization',
+ personalizationEvent: 'personalization'
+ };
+
+ var _iterator3 = _createForOfIteratorHelper(experienceUnomiEvent.target.properties.variants),
+ _step3;
+
+ try {
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
+ var variant = _step3.value;
+ var jsEventDetail = {
+ id: variant.id,
+ name: variant.systemName,
+ displayableName: variant.displayableName,
+ path: variant.path,
+ type: typeMapper[experienceUnomiEvent.eventType],
+ variantType: experienceUnomiEvent.target.properties.type,
+ tags: variant.tags,
+ nodeType: variant.nodeType,
+ wrapper: {
+ id: experienceUnomiEvent.target.itemId,
+ name: experienceUnomiEvent.target.properties.systemName,
+ displayableName: experienceUnomiEvent.target.properties.displayableName,
+ path: experienceUnomiEvent.target.properties.path,
+ tags: experienceUnomiEvent.target.properties.tags,
+ nodeType: experienceUnomiEvent.target.properties.nodeType
+ }
+ };
+ wem.dispatchJSEvent('displayWemVariant', false, false, jsEventDetail);
+ }
+ } catch (err) {
+ _iterator3.e(err);
+ } finally {
+ _iterator3.f();
+ }
+ }
+ },
+ _filterUnomiEvents: function _filterUnomiEvents() {
+ if (wem.digitalData && wem.digitalData.events) {
+ wem.digitalData.events = wem.digitalData.events.filter(function (event) {
+ return !event.properties || !event.properties.doNotSendToUnomi;
+ }).map(function (event) {
+ if (event.properties) {
+ delete event.properties.doNotSendToUnomi;
+ }
+
+ return event;
+ });
+ }
+ },
+ _completeEvent: function _completeEvent(event) {
+ if (!event.source) {
+ event.source = wem.buildSourcePage();
+ }
+
+ if (!event.scope) {
+ event.scope = wem.digitalData.scope;
+ }
+
+ if (event.target && !event.target.scope) {
+ event.target.scope = wem.digitalData.scope;
+ }
+
+ return event;
+ },
+ _registerEvent: function _registerEvent(event, unshift) {
+ if (wem.digitalData) {
+ if (wem.cxs) {
+ console.error('[WEM] already loaded, too late...');
+ return;
+ }
+ } else {
+ wem.digitalData = {};
+ }
+
+ wem.digitalData.events = wem.digitalData.events || [];
+
+ if (unshift) {
+ wem.digitalData.events.unshift(event);
+ } else {
+ wem.digitalData.events.push(event);
+ }
+ },
+ _registerCallback: function _registerCallback(onLoadCallback) {
+ if (wem.digitalData) {
+ if (wem.cxs) {
+ console.info('[WEM] digitalData object loaded, calling on load callback immediately and registering update callback...');
+
+ if (onLoadCallback) {
+ onLoadCallback(wem.digitalData);
+ }
+ } else {
+ console.info('[WEM] digitalData object present but not loaded, registering load callback...');
+
+ if (onLoadCallback) {
+ wem.digitalData.loadCallbacks = wem.digitalData.loadCallbacks || [];
+ wem.digitalData.loadCallbacks.push(onLoadCallback);
+ }
+ }
+ } else {
+ console.info('[WEM] No digital data object found, creating and registering update callback...');
+ wem.digitalData = {};
+
+ if (onLoadCallback) {
+ wem.digitalData.loadCallbacks = [];
+ wem.digitalData.loadCallbacks.push(onLoadCallback);
+ }
+ }
+ },
+ _registerPersonalizationCallback: function _registerPersonalizationCallback(personalization, callback) {
+ if (wem.digitalData) {
+ if (wem.cxs) {
+ console.error('[WEM] already loaded, too late...');
+ } else {
+ console.info('[WEM] digitalData object present but not loaded, registering sort callback...');
+ wem.digitalData.personalizationCallback = wem.digitalData.personalizationCallback || [];
+ wem.digitalData.personalizationCallback.push({
+ personalization: personalization,
+ callback: callback
+ });
+ }
+ } else {
+ wem.digitalData = {};
+ wem.digitalData.personalizationCallback = wem.digitalData.personalizationCallback || [];
+ wem.digitalData.personalizationCallback.push({
+ personalization: personalization,
+ callback: callback
+ });
+ }
+ },
+ _buildObject: function _buildObject(itemId, itemType, properties) {
+ var object = {
+ scope: wem.digitalData.scope,
+ itemId: itemId,
+ itemType: itemType
+ };
+
+ if (properties) {
+ object.properties = properties;
+ }
+
+ return object;
+ },
+ _onSuccess: function _onSuccess(xhr) {
+ wem.cxs = JSON.parse(xhr.responseText);
+
+ if (wem.digitalData.loadCallbacks && wem.digitalData.loadCallbacks.length > 0) {
+ console.info('[WEM] Found context server load callbacks, calling now...');
+
+ if (wem.digitalData.loadCallbacks) {
+ for (var i = 0; i < wem.digitalData.loadCallbacks.length; i++) {
+ wem.digitalData.loadCallbacks[i](wem.digitalData);
+ }
+ }
+
+ if (wem.digitalData.personalizationCallback) {
+ for (var j = 0; j < wem.digitalData.personalizationCallback.length; j++) {
+ wem.digitalData.personalizationCallback[j].callback(wem.cxs.personalizations[wem.digitalData.personalizationCallback[j].personalization.id]);
+ }
+ }
+ } // Put a marker to be able to know when wem is full loaded, context is loaded, and callbacks have been executed.
+
+
+ window.wemLoaded = true;
+ },
+ _executeFallback: function _executeFallback(logMessage) {
+ console.warn('[WEM] execute fallback' + (logMessage ? ': ' + logMessage : ''));
+ wem.fallback = true;
+ wem.cxs = {};
+
+ for (var index in wem.digitalData.loadCallbacks) {
+ wem.digitalData.loadCallbacks[index]();
+ }
+
+ if (wem.digitalData.personalizationCallback) {
+ for (var i = 0; i < wem.digitalData.personalizationCallback.length; i++) {
+ wem.digitalData.personalizationCallback[i].callback([wem.digitalData.personalizationCallback[i].personalization.strategyOptions.fallback]);
+ }
+ }
+ },
+ _processReferrer: function _processReferrer() {
+ var referrerURL = wem.digitalData.page.pageInfo.referringURL || document.referrer;
+ var sameDomainReferrer = false;
+
+ if (referrerURL) {
+ // parse referrer URL
+ var referrer = new URL(referrerURL); // Set sameDomainReferrer property
+
+ sameDomainReferrer = referrer.host === window.location.host; // only process referrer if it's not coming from the same site as the current page
+
+ if (!sameDomainReferrer) {
+ // get search element if it exists and extract search query if available
+ var search = referrer.search;
+ var query = undefined;
+
+ if (search && search != '') {
+ // parse parameters
+ var queryParams = [],
+ param;
+ var queryParamPairs = search.slice(1).split('&');
+
+ for (var i = 0; i < queryParamPairs.length; i++) {
+ param = queryParamPairs[i].split('=');
+ queryParams.push(param[0]);
+ queryParams[param[0]] = param[1];
+ } // try to extract query: q is Google-like (most search engines), p is Yahoo
+
+
+ query = queryParams.q || queryParams.p;
+ query = decodeURIComponent(query).replace(/\+/g, ' ');
+ } // register referrer event
+ // Create deep copy of wem.digitalData.page and add data to pageInfo sub object
+
+
+ if (wem.digitalData && wem.digitalData.page && wem.digitalData.page.pageInfo) {
+ wem.digitalData.page.pageInfo.referrerHost = referrer.host;
+ wem.digitalData.page.pageInfo.referrerQuery = query;
+ }
+ }
+ }
+
+ wem.digitalData.page.pageInfo.sameDomainReferrer = sameDomainReferrer;
+ },
+ _formSubmitEventListener: function _formSubmitEventListener(event) {
+ console.info('[WEM] Registering form event callback');
+ var form = event.target;
+ var formName = form.getAttribute('name') ? form.getAttribute('name') : form.getAttribute('id');
+
+ if (formName && wem.formNamesToWatch.indexOf(formName) > -1) {
+ console.info('[WEM] catching form ' + formName);
+ var eventCopy = document.createEvent('Event'); // Define that the event name is 'build'.
+
+ eventCopy.initEvent('submit', event.bubbles, event.cancelable);
+ event.stopImmediatePropagation();
+ event.preventDefault();
+ var formEvent = wem.buildFormEvent(formName); // merge form properties with event properties
+
+ formEvent.flattenedProperties = {
+ fields: wem._extractFormData(form)
+ };
+ wem.collectEvent(formEvent, function () {
+ form.removeEventListener('submit', wem._formSubmitEventListener, true);
+ form.dispatchEvent(eventCopy);
+
+ if (!eventCopy.defaultPrevented && !eventCopy.cancelBubble) {
+ form.submit();
+ }
+
+ form.addEventListener('submit', wem._formSubmitEventListener, true);
+ }, function (xhr) {
+ console.error('[WEM] Error while collecting form event: ' + xhr.status + ' ' + xhr.statusText);
+ xhr.abort();
+ form.removeEventListener('submit', wem._formSubmitEventListener, true);
+ form.dispatchEvent(eventCopy);
+
+ if (!eventCopy.defaultPrevented && !eventCopy.cancelBubble) {
+ form.submit();
+ }
+
+ form.addEventListener('submit', wem._formSubmitEventListener, true);
+ });
+ }
+ },
+ _extractFormData: function _extractFormData(form) {
+ var params = {};
+
+ for (var i = 0; i < form.elements.length; i++) {
+ var e = form.elements[i]; // ignore empty and undefined key (e.name)
+
+ if (e.name) {
+ switch (e.nodeName) {
+ case 'TEXTAREA':
+ case 'INPUT':
+ switch (e.type) {
+ case 'checkbox':
+ var checkboxes = document.querySelectorAll('input[name="' + e.name + '"]');
+
+ if (checkboxes.length > 1) {
+ if (!params[e.name]) {
+ params[e.name] = [];
+ }
+
+ if (e.checked) {
+ params[e.name].push(e.value);
+ }
+ }
+
+ break;
+
+ case 'radio':
+ if (e.checked) {
+ params[e.name] = e.value;
+ }
+
+ break;
+
+ default:
+ if (!e.value || e.value == '') {
+ // ignore element if no value is provided
+ break;
+ }
+
+ params[e.name] = e.value;
+ }
+
+ break;
+
+ case 'SELECT':
+ if (e.options && e.options[e.selectedIndex]) {
+ if (e.multiple) {
+ params[e.name] = [];
+
+ for (var j = 0; j < e.options.length; j++) {
+ if (e.options[j].selected) {
+ params[e.name].push(e.options[j].value);
+ }
+ }
+ } else {
+ params[e.name] = e.options[e.selectedIndex].value;
+ }
+ }
+
+ break;
+ }
+ }
+ }
+
+ return params;
+ },
+ _resolveId: function _resolveId(id) {
+ if (wem.digitalData.sourceLocalIdentifierMap) {
+ var source = Object.keys(wem.digitalData.sourceLocalIdentifierMap).filter(function (source) {
+ return id.indexOf(source) > 0;
+ });
+ return source ? id.replace(source, wem.digitalData.sourceLocalIdentifierMap[source]) : id;
+ }
+
+ return id;
+ },
+ _enableWem: function _enableWem(enable, callback) {
+ // display fallback if wem is not enable
+ wem.fallback = !enable; // remove cookies, reset cxs
+
+ if (!enable) {
+ wem.cxs = {};
+ document.cookie = wem.trackerProfileIdCookieName + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
+ document.cookie = wem.contextServerCookieName + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
+ delete wem.contextLoaded;
+ } else {
+ if (wem.DOMLoaded) {
+ wem.loadContext();
+ } else {
+ // As Dom loaded listener not triggered, enable global value.
+ wem.activateWem = true;
+ }
+ }
+
+ if (callback) {
+ callback(enable);
+ }
+
+ console.log("Wem ".concat(enable ? 'enabled' : 'disabled'));
+ },
+ _isInControlGroup: function _isInControlGroup(id) {
+ if (wem.cxs.profileProperties && wem.cxs.profileProperties.unomiControlGroups) {
+ var controlGroup = wem.cxs.profileProperties.unomiControlGroups.find(function (controlGroup) {
+ return controlGroup.id === id;
+ });
+
+ if (controlGroup) {
+ return true;
+ }
+ }
+
+ if (wem.cxs.sessionProperties && wem.cxs.sessionProperties.unomiControlGroups) {
+ var _controlGroup = wem.cxs.sessionProperties.unomiControlGroups.find(function (controlGroup) {
+ return controlGroup.id === id;
+ });
+
+ if (_controlGroup) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ };
+ return wem;
+ };
+
+ /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ var useTracker = function useTracker() {
+ return newTracker();
+ };
+
+ exports.useTracker = useTracker;
+
+ Object.defineProperty(exports, '__esModule', { value: true });
+
+}));