This is an automated email from the ASF dual-hosted git repository.
poorejc pushed a commit to branch FLAGON-336
in repository https://gitbox.apache.org/repos/asf/incubator-flagon-useralejs.git
The following commit(s) were added to refs/heads/FLAGON-336 by this push:
new 8e439fe [FLAGON-376] include pre-build web extension and userale.js
script in src
8e439fe is described below
commit 8e439feccc6b10f3ff7ad66143c22248f27dea65
Author: poorejc <[email protected]>
AuthorDate: Fri Apr 19 22:08:49 2019 -0400
[FLAGON-376] include pre-build web extension and userale.js script in src
---
build/UserAleWebExtension/background.js | 64 ++-
build/UserAleWebExtension/content.js | 123 ++++--
build/UserAleWebExtension/options.js | 4 +-
build/userale-1.1.0.js | 745 ++++++++++++++++++++++++++++++++
build/userale-1.1.0.min.js | 1 +
5 files changed, 858 insertions(+), 79 deletions(-)
diff --git a/build/UserAleWebExtension/background.js
b/build/UserAleWebExtension/background.js
index 633dc71..0fffa4f 100644
--- a/build/UserAleWebExtension/background.js
+++ b/build/UserAleWebExtension/background.js
@@ -14,6 +14,23 @@ var prefix = 'USERALE_';
var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE';
var ADD_LOG = prefix + 'ADD_LOG';
+/*
+ * 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.
+ */
+
/**
* Creates a function to normalize the timestamp of the provided event.
* @param {Object} e An event containing a timeStamp property.
@@ -37,11 +54,11 @@ function timeStampScale(e) {
var navStart = performance.timing.navigationStart;
tsScaler = function (ts) {
return ts + navStart;
- }
+ };
} else {
tsScaler = function (ts) {
return ts;
- }
+ };
}
} else {
tsScaler = function () { return Date.now(); };
@@ -67,38 +84,6 @@ function timeStampScale(e) {
* limitations under the License.
*/
-var logs$1;
-var config$1;
-
-// Interval Logging Globals
-var intervalID;
-var intervalType;
-var intervalPath;
-var intervalTimer;
-var intervalCounter;
-var intervalLog;
-
-var filterHandler = null;
-var mapHandler = null;
-
-/**
- * Assigns the config and log container to be used by the logging functions.
- * @param {Array} newLogs Log container.
- * @param {Object} newConfig Configuration to use while logging.
- */
-function initPackager(newLogs, newConfig) {
- logs$1 = newLogs;
- config$1 = newConfig;
- filterHandler = null;
- mapHandler = null;
- intervalID = null;
- intervalType = null;
- intervalPath = null;
- intervalTimer = null;
- intervalCounter = 0;
- intervalLog = null;
-}
-
/**
* Extract the millisecond and microsecond portions of a timestamp.
* @param {Number} timeStamp The timestamp to split into millisecond and
microsecond fields.
@@ -184,7 +169,7 @@ function sendOnClose(logs, config) {
if (logs.length > 0) {
sendLogs(logs, config.url, 1);
}
- })
+ });
}
}
@@ -214,6 +199,10 @@ function sendLogs(logs, url, retries) {
req.send(data);
}
+/*
+ eslint-disable
+ */
+
// inherent dependency on globals.js, loaded by the webext
// browser is defined in firefox, but not in chrome. In chrome, they use
@@ -257,8 +246,6 @@ function storeCallback(item) {
toolName: item.toolName,
toolVersion: item.toolVersion
});
-
- initPackager(logs, config);
initSender(logs, config);
}
@@ -301,7 +288,6 @@ browser.runtime.onMessage.addListener(function (message) {
toolName: message.payload.toolName,
toolVersion: message.payload.toolVersion
});
- initPackager(logs, updatedConfig);
initSender(logs, updatedConfig);
dispatchTabMessage(message);
})();
@@ -385,4 +371,4 @@ browser.tabs.onZoomChange.addListener(function (e) {
/*
eslint-enable
- */
\ No newline at end of file
+ */
diff --git a/build/UserAleWebExtension/content.js
b/build/UserAleWebExtension/content.js
index 9a96e39..bfe21bf 100644
--- a/build/UserAleWebExtension/content.js
+++ b/build/UserAleWebExtension/content.js
@@ -14,7 +14,7 @@ var prefix = 'USERALE_';
var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE';
var ADD_LOG = prefix + 'ADD_LOG';
-var version$1 = "1.1.0";
+var version = "1.1.0";
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
@@ -87,11 +87,11 @@ function timeStampScale(e) {
var navStart = performance.timing.navigationStart;
tsScaler = function (ts) {
return ts + navStart;
- }
+ };
} else {
tsScaler = function (ts) {
return ts;
- }
+ };
}
} else {
tsScaler = function () { return Date.now(); };
@@ -169,8 +169,8 @@ function getUserIdFromParams(param) {
* limitations under the License.
*/
-var logs$1;
-var config$1;
+var logs;
+var config;
// Interval Logging Globals
var intervalID;
@@ -191,14 +191,15 @@ function setLogFilter(callback) {
filterHandler = callback;
}
+
/**
* Assigns the config and log container to be used by the logging functions.
* @param {Array} newLogs Log container.
* @param {Object} newConfig Configuration to use while logging.
*/
function initPackager(newLogs, newConfig) {
- logs$1 = newLogs;
- config$1 = newConfig;
+ logs = newLogs;
+ config = newConfig;
filterHandler = null;
mapHandler = null;
intervalID = null;
@@ -216,7 +217,7 @@ function initPackager(newLogs, newConfig) {
* @return {boolean} Whether the event was logged.
*/
function packageLog(e, detailFcn) {
- if (!config$1.on) {
+ if (!config.on) {
return false;
}
@@ -226,12 +227,15 @@ function packageLog(e, detailFcn) {
}
var timeFields = extractTimeFields(
- (e.timeStamp && e.timeStamp > 0) ? config$1.time(e.timeStamp) : Date.now()
+ (e.timeStamp && e.timeStamp > 0) ? config.time(e.timeStamp) : Date.now()
);
var log = {
'target' : getSelector(e.target),
'path' : buildPath(e),
+ 'pageUrl': window.location.href,
+ 'pageTitle': document.title,
+ 'pageReferrer': document.referrer,
'clientTime' : timeFields.milli,
'microTime' : timeFields.micro,
'location' : getLocation(e),
@@ -239,11 +243,11 @@ function packageLog(e, detailFcn) {
'logType': 'raw',
'userAction' : true,
'details' : details,
- 'userId' : config$1.userId,
- 'toolVersion' : config$1.version,
- 'toolName' : config$1.toolName,
- 'useraleVersion': config$1.useraleVersion,
- 'sessionID': config$1.sessionID
+ 'userId' : config.userId,
+ 'toolVersion' : config.version,
+ 'toolName' : config.toolName,
+ 'useraleVersion': config.useraleVersion,
+ 'sessionID': config.sessionID
};
if ((typeof filterHandler === 'function') && !filterHandler(log)) {
@@ -254,7 +258,7 @@ function packageLog(e, detailFcn) {
log = mapHandler(log);
}
- logs$1.push(log);
+ logs.push(log);
return true;
}
@@ -281,7 +285,7 @@ function packageIntervalLog(e) {
var target = getSelector(e.target);
var path = buildPath(e);
var type = e.type;
- var timestamp = Math.floor((e.timeStamp && e.timeStamp > 0) ?
config$1.time(e.timeStamp) : Date.now());
+ var timestamp = Math.floor((e.timeStamp && e.timeStamp > 0) ?
config.time(e.timeStamp) : Date.now());
// Init - this should only happen once on initialization
if (intervalID == null) {
@@ -299,6 +303,9 @@ function packageIntervalLog(e) {
intervalLog = {
'target': intervalID,
'path': intervalPath,
+ 'pageUrl': window.location.href,
+ 'pageTitle': document.title,
+ 'pageReferrer': document.referrer,
'count': intervalCounter,
'duration': timestamp - intervalTimer, // microseconds
'startTime': intervalTimer,
@@ -308,11 +315,11 @@ function packageIntervalLog(e) {
'targetChange': intervalID !== target,
'typeChange': intervalType !== type,
'userAction': false,
- 'userId': config$1.userId,
- 'toolVersion': config$1.version,
- 'toolName': config$1.toolName,
- 'useraleVersion': config$1.useraleVersion,
- 'sessionID': config$1.sessionID
+ 'userId': config.userId,
+ 'toolVersion': config.version,
+ 'toolName': config.toolName,
+ 'useraleVersion': config.useraleVersion,
+ 'sessionID': config.sessionID
};
if (typeof filterHandler === 'function' &&
!filterHandler(intervalLog)) {
@@ -323,7 +330,7 @@ function packageIntervalLog(e) {
intervalLog = mapHandler(intervalLog);
}
- logs$1.push(intervalLog);
+ logs.push(intervalLog);
// Reset
intervalID = target;
@@ -384,7 +391,7 @@ function buildPath(e) {
if (e.path) {
path = e.path;
} else {
- var ele = e.target
+ var ele = e.target;
while(ele) {
path.push(ele);
ele = ele.parentElement;
@@ -410,6 +417,23 @@ function selectorizePath(path) {
return pathSelectors;
}
+/*
+ * 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 events;
var bufferBools;
var bufferedEvents;
@@ -579,7 +603,7 @@ function sendOnClose(logs, config) {
if (logs.length > 0) {
sendLogs(logs, config.url, 1);
}
- })
+ });
}
}
@@ -609,18 +633,36 @@ function sendLogs(logs, url, retries) {
req.send(data);
}
-var config = {};
-var logs = [];
+/*
+ * 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 config$1 = {};
+var logs$1 = [];
var started = false;
+
// Start up Userale
-config.on = false;
-config.useraleVersion = version$1;
+config$1.on = false;
+config$1.useraleVersion = version;
-configure(config, getInitialSettings());
-initPackager(logs, config);
+configure(config$1, getInitialSettings());
+initPackager(logs$1, config$1);
-if (config.autostart) {
- setup(config);
+if (config$1.autostart) {
+ setup(config$1);
}
/**
@@ -635,7 +677,7 @@ function setup(config) {
if (state === 'interactive' || state === 'complete') {
attachHandlers(config);
- initSender(logs, config);
+ initSender(logs$1, config);
started = config.on = true;
} else {
setup(config);
@@ -644,17 +686,16 @@ function setup(config) {
}
}
-
/**
* Used to start the logging process if the
* autostart configuration option is set to false.
*/
function start() {
if (!started) {
- setup(config);
+ setup(config$1);
}
- config.on = true;
+ config$1.on = true;
}
/**
@@ -665,12 +706,16 @@ function start() {
*/
function options(newConfig) {
if (newConfig !== undefined) {
- configure(config, newConfig);
+ configure(config$1, newConfig);
}
- return config;
+ return config$1;
}
+/*
+ eslint-disable
+ */
+
// browser is defined in firefox, but not in chrome. In chrome, they use
// the 'chrome' global instead. Let's map it to browser so we don't have
// to have if-conditions all over the place.
@@ -726,4 +771,4 @@ browser.runtime.onMessage.addListener(function (message) {
/*
eslint-enable
- */
\ No newline at end of file
+ */
diff --git a/build/UserAleWebExtension/options.js
b/build/UserAleWebExtension/options.js
index 0c8bbd6..4fd1ef2 100644
--- a/build/UserAleWebExtension/options.js
+++ b/build/UserAleWebExtension/options.js
@@ -13,6 +13,8 @@ var prefix = 'USERALE_';
var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE';
+/* eslint-disable */
+
if (chrome) {
browser = chrome;
}
@@ -53,4 +55,4 @@ document.addEventListener("submit", function() {
saveOptions();
});
-/* eslint-enable */
\ No newline at end of file
+/* eslint-enable */
diff --git a/build/userale-1.1.0.js b/build/userale-1.1.0.js
new file mode 100644
index 0000000..90ebfbe
--- /dev/null
+++ b/build/userale-1.1.0.js
@@ -0,0 +1,745 @@
+var userale = (function (exports) {
+ 'use strict';
+
+ var version = "1.1.0";
+
+ /*
+ * 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.
+ */
+
+ /**
+ * Extracts the initial configuration settings from the
+ * currently executing script tag.
+ * @return {Object} The extracted configuration object
+ */
+ function getInitialSettings() {
+ var settings = {};
+
+ var script = document.currentScript || (function () {
+ var scripts = document.getElementsByTagName('script');
+ return scripts[scripts.length - 1];
+ })();
+
+ var get = script ? script.getAttribute.bind(script) : function() { return
null; };
+
+ settings.autostart = get('data-autostart') === 'false' ? false : true;
+ settings.url = get('data-url') || 'http://localhost:8000';
+ settings.transmitInterval = +get('data-interval') || 5000;
+ settings.logCountThreshold = +get('data-threshold') || 5;
+ settings.userId = get('data-user') || null;
+ settings.version = get('data-version') || null;
+ settings.logDetails = get('data-log-details') === 'true' ? true : false;
+ settings.resolution = +get('data-resolution') || 500;
+ settings.toolName = get('data-tool') || null;
+ settings.userFromParams = get('data-user-from-params') || null;
+ settings.time = timeStampScale(document.createEvent('CustomEvent'));
+ settings.sessionID = get('data-session') || 'session_' +
String(Date.now());
+
+ return settings;
+ }
+
+ /**
+ * Creates a function to normalize the timestamp of the provided event.
+ * @param {Object} e An event containing a timeStamp property.
+ * @return {timeStampScale~tsScaler} The timestamp normalizing function.
+ */
+ function timeStampScale(e) {
+ if (e.timeStamp && e.timeStamp > 0) {
+ var delta = Date.now() - e.timeStamp;
+ /**
+ * Returns a timestamp depending on various browser quirks.
+ * @param {?Number} ts A timestamp to use for normalization.
+ * @return {Number} A normalized timestamp.
+ */
+ var tsScaler;
+
+ if (delta < 0) {
+ tsScaler = function () {
+ return e.timeStamp / 1000;
+ };
+ } else if (delta > e.timeStamp) {
+ var navStart = performance.timing.navigationStart;
+ tsScaler = function (ts) {
+ return ts + navStart;
+ };
+ } else {
+ tsScaler = function (ts) {
+ return ts;
+ };
+ }
+ } else {
+ tsScaler = function () { return Date.now(); };
+ }
+
+ return tsScaler;
+ }
+
+ /*
+ * 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.
+ */
+
+ /**
+ * Shallow merges the first argument with the second.
+ * Retrieves/updates the userid if userFromParams is provided.
+ * @param {Object} config Current configuration object to be merged into.
+ * @param {Object} newConfig Configuration object to merge into the current
config.
+ */
+ function configure(config, newConfig) {
+ Object.keys(newConfig).forEach(function(option) {
+ if (option === 'userFromParams') {
+ var userId = getUserIdFromParams(newConfig[option]);
+ if (userId) {
+ config.userId = userId;
+ }
+ }
+ config[option] = newConfig[option];
+ });
+ }
+
+ /**
+ * Attempts to extract the userid from the query parameters of the URL.
+ * @param {string} param The name of the query parameter containing the
userid.
+ * @return {string|null} The extracted/decoded userid, or null if none
is found.
+ */
+ function getUserIdFromParams(param) {
+ var userField = param;
+ var regex = new RegExp('[?&]' + userField + '(=([^&#]*)|&|#|$)');
+ var results = window.location.href.match(regex);
+
+ if (results && results[2]) {
+ return decodeURIComponent(results[2].replace(/\+/g, ' '));
+ } else {
+ return null;
+ }
+ }
+
+ /*
+ * 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 logs;
+ var config;
+
+ // Interval Logging Globals
+ var intervalID;
+ var intervalType;
+ var intervalPath;
+ var intervalTimer;
+ var intervalCounter;
+ var intervalLog;
+
+ var filterHandler = null;
+ var mapHandler = null;
+
+ /**
+ * Assigns a handler to filter logs out of the queue.
+ * @param {Function} callback The handler to invoke when logging.
+ */
+ function setLogFilter(callback) {
+ filterHandler = callback;
+ }
+
+ /**
+ * Assigns a handler to transform logs from their default structure.
+ * @param {Function} callback The handler to invoke when logging.
+ */
+ function setLogMapper(callback) {
+ mapHandler = callback;
+ }
+
+
+ /**
+ * Assigns the config and log container to be used by the logging functions.
+ * @param {Array} newLogs Log container.
+ * @param {Object} newConfig Configuration to use while logging.
+ */
+ function initPackager(newLogs, newConfig) {
+ logs = newLogs;
+ config = newConfig;
+ filterHandler = null;
+ mapHandler = null;
+ intervalID = null;
+ intervalType = null;
+ intervalPath = null;
+ intervalTimer = null;
+ intervalCounter = 0;
+ intervalLog = null;
+ }
+
+ /**
+ * Transforms the provided event into a log and appends it to the log
container.
+ * @param {Object} e The event to be logged.
+ * @param {Function} detailFcn The function to extract additional log
parameters from the event.
+ * @return {boolean} Whether the event was logged.
+ */
+ function packageLog(e, detailFcn) {
+ if (!config.on) {
+ return false;
+ }
+
+ var details = null;
+ if (detailFcn) {
+ details = detailFcn(e);
+ }
+
+ var timeFields = extractTimeFields(
+ (e.timeStamp && e.timeStamp > 0) ? config.time(e.timeStamp) : Date.now()
+ );
+
+ var log = {
+ 'target' : getSelector(e.target),
+ 'path' : buildPath(e),
+ 'pageUrl': window.location.href,
+ 'pageTitle': document.title,
+ 'pageReferrer': document.referrer,
+ 'clientTime' : timeFields.milli,
+ 'microTime' : timeFields.micro,
+ 'location' : getLocation(e),
+ 'type' : e.type,
+ 'logType': 'raw',
+ 'userAction' : true,
+ 'details' : details,
+ 'userId' : config.userId,
+ 'toolVersion' : config.version,
+ 'toolName' : config.toolName,
+ 'useraleVersion': config.useraleVersion,
+ 'sessionID': config.sessionID
+ };
+
+ if ((typeof filterHandler === 'function') && !filterHandler(log)) {
+ return false;
+ }
+
+ if (typeof mapHandler === 'function') {
+ log = mapHandler(log);
+ }
+
+ logs.push(log);
+
+ return true;
+ }
+
+ /**
+ * Extract the millisecond and microsecond portions of a timestamp.
+ * @param {Number} timeStamp The timestamp to split into millisecond and
microsecond fields.
+ * @return {Object} An object containing the millisecond
+ * and microsecond portions of the timestamp.
+ */
+ function extractTimeFields(timeStamp) {
+ return {
+ milli: Math.floor(timeStamp),
+ micro: Number((timeStamp % 1).toFixed(3)),
+ };
+ }
+
+ /**
+ * Track intervals and gather details about it.
+ * @param {Object} e
+ * @return boolean
+ */
+ function packageIntervalLog(e) {
+ var target = getSelector(e.target);
+ var path = buildPath(e);
+ var type = e.type;
+ var timestamp = Math.floor((e.timeStamp && e.timeStamp > 0) ?
config.time(e.timeStamp) : Date.now());
+
+ // Init - this should only happen once on initialization
+ if (intervalID == null) {
+ intervalID = target;
+ intervalType = type;
+ intervalPath = path;
+ intervalTimer = timestamp;
+ intervalCounter = 0;
+ }
+
+ if (intervalID !== target || intervalType !== type) {
+ // When to create log? On transition end
+ // @todo Possible for intervalLog to not be pushed in the event the
interval never ends...
+
+ intervalLog = {
+ 'target': intervalID,
+ 'path': intervalPath,
+ 'pageUrl': window.location.href,
+ 'pageTitle': document.title,
+ 'pageReferrer': document.referrer,
+ 'count': intervalCounter,
+ 'duration': timestamp - intervalTimer, // microseconds
+ 'startTime': intervalTimer,
+ 'endTime': timestamp,
+ 'type': intervalType,
+ 'logType': 'interval',
+ 'targetChange': intervalID !== target,
+ 'typeChange': intervalType !== type,
+ 'userAction': false,
+ 'userId': config.userId,
+ 'toolVersion': config.version,
+ 'toolName': config.toolName,
+ 'useraleVersion': config.useraleVersion,
+ 'sessionID': config.sessionID
+ };
+
+ if (typeof filterHandler === 'function' &&
!filterHandler(intervalLog)) {
+ return false;
+ }
+
+ if (typeof mapHandler === 'function') {
+ intervalLog = mapHandler(intervalLog);
+ }
+
+ logs.push(intervalLog);
+
+ // Reset
+ intervalID = target;
+ intervalType = type;
+ intervalPath = path;
+ intervalTimer = timestamp;
+ intervalCounter = 0;
+ }
+
+ // Interval is still occuring, just update counter
+ if (intervalID == target && intervalType == type) {
+ intervalCounter = intervalCounter + 1;
+ }
+
+ return true;
+ }
+
+ /**
+ * Extracts coordinate information from the event
+ * depending on a few browser quirks.
+ * @param {Object} e The event to extract coordinate information from.
+ * @return {Object} An object containing nullable x and y coordinates for
the event.
+ */
+ function getLocation(e) {
+ if (e.pageX != null) {
+ return { 'x' : e.pageX, 'y' : e.pageY };
+ } else if (e.clientX != null) {
+ return { 'x' : document.documentElement.scrollLeft + e.clientX, 'y' :
document.documentElement.scrollTop + e.clientY };
+ } else {
+ return { 'x' : null, 'y' : null };
+ }
+ }
+
+ /**
+ * Builds a string CSS selector from the provided element
+ * @param {HTMLElement} ele The element from which the selector is built.
+ * @return {string} The CSS selector for the element, or Unknown if it
can't be determined.
+ */
+ function getSelector(ele) {
+ if (ele.localName) {
+ return ele.localName + (ele.id ? ('#' + ele.id) : '') + (ele.className ?
('.' + ele.className) : '');
+ } else if (ele.nodeName) {
+ return ele.nodeName + (ele.id ? ('#' + ele.id) : '') + (ele.className ?
('.' + ele.className) : '');
+ } else if (ele && ele.document && ele.location && ele.alert &&
ele.setInterval) {
+ return "Window";
+ } else {
+ return "Unknown";
+ }
+ }
+
+ /**
+ * Builds an array of elements from the provided event target, to the root
element.
+ * @param {Object} e Event from which the path should be built.
+ * @return {HTMLElement[]} Array of elements, starting at the event
target, ending at the root element.
+ */
+ function buildPath(e) {
+ var path = [];
+ if (e.path) {
+ path = e.path;
+ } else {
+ var ele = e.target;
+ while(ele) {
+ path.push(ele);
+ ele = ele.parentElement;
+ }
+ }
+
+ return selectorizePath(path);
+ }
+
+ /**
+ * Builds a CSS selector path from the provided list of elements.
+ * @param {HTMLElement[]} path Array of HTMLElements from which the path
should be built.
+ * @return {string[]} Array of string CSS selectors.
+ */
+ function selectorizePath(path) {
+ var i = 0;
+ var pathEle;
+ var pathSelectors = [];
+ while (pathEle = path[i]) {
+ pathSelectors.push(getSelector(pathEle));
+ ++i;
+ }
+ return pathSelectors;
+ }
+
+ /*
+ * 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 events;
+ var bufferBools;
+ var bufferedEvents;
+ //@todo: Investigate drag events and their behavior
+ var intervalEvents = ['click', 'focus', 'blur', 'input', 'change',
'mouseover', 'submit'];
+ var windowEvents = ['load', 'blur', 'focus'];
+
+ /**
+ * Maps an event to an object containing useful information.
+ * @param {Object} e Event to extract data from
+ */
+ function extractMouseEvent(e) {
+ return {
+ 'clicks' : e.detail,
+ 'ctrl' : e.ctrlKey,
+ 'alt' : e.altKey,
+ 'shift' : e.shiftKey,
+ 'meta' : e.metaKey
+ };
+ }
+
+ /**
+ * Defines the way information is extracted from various events.
+ * Also defines which events we will listen to.
+ * @param {Object} config Configuration object to read from.
+ */
+ function defineDetails(config) {
+ // Events list
+ // Keys are event types
+ // Values are functions that return details object if applicable
+ events = {
+ 'click' : extractMouseEvent,
+ 'dblclick' : extractMouseEvent,
+ 'mousedown' : extractMouseEvent,
+ 'mouseup' : extractMouseEvent,
+ 'focus' : null,
+ 'blur' : null,
+ 'input' : config.logDetails ? function(e) { return { 'value' :
e.target.value }; } : null,
+ 'change' : config.logDetails ? function(e) { return { 'value' :
e.target.value }; } : null,
+ 'dragstart' : null,
+ 'dragend' : null,
+ 'drag' : null,
+ 'drop' : null,
+ 'keydown' : config.logDetails ? function(e) { return { 'key' :
e.keyCode, 'ctrl' : e.ctrlKey, 'alt' : e.altKey, 'shift' : e.shiftKey, 'meta' :
e.metaKey }; } : null,
+ 'mouseover' : null,
+ 'submit' : null
+ };
+
+ bufferBools = {};
+ bufferedEvents = {
+ 'wheel' : function(e) { return { 'x' : e.deltaX, 'y' : e.deltaY, 'z' :
e.deltaZ }; },
+ 'scroll' : function() { return { 'x' : window.scrollX, 'y' :
window.scrollY }; },
+ 'resize' : function() { return { 'width' : window.outerWidth, 'height' :
window.outerHeight }; }
+ };
+ }
+
+ /**
+ * Hooks the event handlers for each event type of interest.
+ * @param {Object} config Configuration object to use.
+ * @return {boolean} Whether the operation succeeded
+ */
+ function attachHandlers(config) {
+ defineDetails(config);
+
+ Object.keys(events).forEach(function(ev) {
+ document.addEventListener(ev, function(e) {
+ packageLog(e, events[ev]);
+ }, true);
+ });
+
+ intervalEvents.forEach(function(ev) {
+ document.addEventListener(ev, function(e) {
+ packageIntervalLog(e);
+ }, true);
+ });
+
+ Object.keys(bufferedEvents).forEach(function(ev) {
+ bufferBools[ev] = true;
+
+ window.addEventListener(ev, function(e) {
+ if (bufferBools[ev]) {
+ bufferBools[ev] = false;
+ packageLog(e, bufferedEvents[ev]);
+ setTimeout(function() { bufferBools[ev] = true; },
config.resolution);
+ }
+ }, true);
+ });
+
+ windowEvents.forEach(function(ev) {
+ window.addEventListener(ev, function(e) {
+ packageLog(e, function() { return { 'window' : true }; });
+ }, true);
+ });
+
+ return true;
+ }
+
+ /*
+ * 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 sendIntervalId = null;
+
+ /**
+ * Initializes the log queue processors.
+ * @param {Array} logs Array of logs to append to.
+ * @param {Object} config Configuration object to use when logging.
+ */
+ function initSender(logs, config) {
+ if (sendIntervalId !== null) {
+ clearInterval(sendIntervalId);
+ }
+
+ sendIntervalId = sendOnInterval(logs, config);
+ sendOnClose(logs, config);
+ }
+
+ /**
+ * Checks the provided log array on an interval, flushing the logs
+ * if the queue has reached the threshold specified by the provided config.
+ * @param {Array} logs Array of logs to read from.
+ * @param {Object} config Configuration object to be read from.
+ * @return {Number} The newly created interval id.
+ */
+ function sendOnInterval(logs, config) {
+ return setInterval(function() {
+ if (!config.on) {
+ return;
+ }
+
+ if (logs.length >= config.logCountThreshold) {
+ sendLogs(logs.slice(0), config.url, 0); // Send a copy
+ logs.splice(0); // Clear array reference (no reassignment)
+ }
+ }, config.transmitInterval);
+ }
+
+ /**
+ * Attempts to flush the remaining logs when the window is closed.
+ * @param {Array} logs Array of logs to be flushed.
+ * @param {Object} config Configuration object to be read from.
+ */
+ function sendOnClose(logs, config) {
+ if (!config.on) {
+ return;
+ }
+
+ if (navigator.sendBeacon) {
+ window.addEventListener('unload', function() {
+ navigator.sendBeacon(config.url, JSON.stringify(logs));
+ });
+ } else {
+ window.addEventListener('beforeunload', function() {
+ if (logs.length > 0) {
+ sendLogs(logs, config.url, 1);
+ }
+ });
+ }
+ }
+
+ /**
+ * Sends the provided array of logs to the specified url,
+ * retrying the request up to the specified number of retries.
+ * @param {Array} logs Array of logs to send.
+ * @param {string} url URL to send the POST request to.
+ * @param {Number} retries Maximum number of attempts to send the logs.
+ */
+ function sendLogs(logs, url, retries) {
+ var req = new XMLHttpRequest();
+
+ var data = JSON.stringify(logs);
+
+ req.open('POST', url);
+ req.setRequestHeader('Content-type', 'application/json;charset=UTF-8');
+
+ req.onreadystatechange = function() {
+ if (req.readyState === 4 && req.status !== 200) {
+ if (retries > 0) {
+ sendLogs(logs, url, retries--);
+ }
+ }
+ };
+
+ req.send(data);
+ }
+
+ /*
+ * 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 config$1 = {};
+ var logs$1 = [];
+ exports.started = false;
+
+ // Start up Userale
+ config$1.on = false;
+ config$1.useraleVersion = version;
+
+ configure(config$1, getInitialSettings());
+ initPackager(logs$1, config$1);
+
+ if (config$1.autostart) {
+ setup(config$1);
+ }
+
+ /**
+ * Hooks the global event listener, and starts up the
+ * logging interval.
+ * @param {Object} config Configuration settings for the logger
+ */
+ function setup(config) {
+ if (!exports.started) {
+ setTimeout(function() {
+ var state = document.readyState;
+
+ if (state === 'interactive' || state === 'complete') {
+ attachHandlers(config);
+ initSender(logs$1, config);
+ exports.started = config.on = true;
+ } else {
+ setup(config);
+ }
+ }, 100);
+ }
+ }
+
+
+ // Export the Userale API
+ var version$1 = version;
+
+ /**
+ * Used to start the logging process if the
+ * autostart configuration option is set to false.
+ */
+ function start() {
+ if (!exports.started) {
+ setup(config$1);
+ }
+
+ config$1.on = true;
+ }
+
+ /**
+ * Halts the logging process. Logs will no longer be sent.
+ */
+ function stop() {
+ config$1.on = false;
+ }
+
+ /**
+ * Updates the current configuration
+ * object with the provided values.
+ * @param {Object} newConfig The configuration options to use.
+ * @return {Object} Returns the updated configuration.
+ */
+ function options(newConfig) {
+ if (newConfig !== undefined) {
+ configure(config$1, newConfig);
+ }
+
+ return config$1;
+ }
+
+ /**
+ * Appends a log to the log queue.
+ * @param {Object} customLog The log to append.
+ * @return {boolean} Whether the operation succeeded.
+ */
+ function log(customLog) {
+ if (customLog !== null && typeof customLog === 'object') {
+ logs$1.push(customLog);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ exports.filter = setLogFilter;
+ exports.log = log;
+ exports.map = setLogMapper;
+ exports.options = options;
+ exports.start = start;
+ exports.stop = stop;
+ exports.version = version$1;
+
+ return exports;
+
+}({}));
diff --git a/build/userale-1.1.0.min.js b/build/userale-1.1.0.min.js
new file mode 100644
index 0000000..e139175
--- /dev/null
+++ b/build/userale-1.1.0.min.js
@@ -0,0 +1 @@
+var userale=function(n){"use strict";var a,i,u,l,s,c,d,f,t="1.1.0";function
e(n,o){Object.keys(o).forEach(function(t){if("userFromParams"===t){var
e=function(t){var e=new
RegExp("[?&]"+t+"(=([^&#]*)|&|#|$)"),n=window.location.href.match(e);return
n&&n[2]?decodeURIComponent(n[2].replace(/\+/g,"
")):null}(o[t]);e&&(n.userId=e)}n[t]=o[t]})}var o,r,m,p=null,g=null;function
v(t,e){if(!i.on)return!1;var n=null;e&&(n=e(t));var
o=function(t){return{milli:Math.floor(t),micro:Number((t%1).toFixed( [...]
\ No newline at end of file