This is an automated email from the ASF dual-hosted git repository.

poorejc pushed a commit to branch FLAGON-434
in repository https://gitbox.apache.org/repos/asf/incubator-flagon-useralejs.git


The following commit(s) were added to refs/heads/FLAGON-434 by this push:
     new d75ebed  [FLAGON-455] Update Index.html to integrate forms example, 
.options API example
d75ebed is described below

commit d75ebed65aec0f5ce956820fd68eefafb76fe9a9
Author: poorejc <[email protected]>
AuthorDate: Fri Sep 27 01:50:05 2019 -0400

    [FLAGON-455] Update Index.html to integrate forms example, .options API 
example
---
 build/UserAleWebExtension/background.js |  788 +++++++--------
 build/UserAleWebExtension/content.js    | 1653 ++++++++++++++++---------------
 build/UserAleWebExtension/options.js    |   38 +-
 build/userale-2.0.2.js                  |   21 +-
 build/userale-2.0.2.min.js              |    2 +-
 example/index.html                      |   55 +-
 example/index_form.html                 |   93 --
 test/testUtils.js                       |    2 +
 8 files changed, 1291 insertions(+), 1361 deletions(-)

diff --git a/build/UserAleWebExtension/background.js 
b/build/UserAleWebExtension/background.js
index ee9e91f..f8d7bcb 100644
--- a/build/UserAleWebExtension/background.js
+++ b/build/UserAleWebExtension/background.js
@@ -26,415 +26,397 @@ var toolVersion = '2.0.2';
 
 /* eslint-enable */
 
-/*
-* 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 prefix = 'USERALE_';
-
-var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE';
+/*
+* 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 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.
- */
-
-var SessionId = getSessionId("userAleSessionId", 'session_' + 
String(Date.now()));
-
-/**
- * defines sessionId, stores it in sessionStorage, checks to see if there is a 
sessionId in
- * storage when script is started. This prevents events like 'submit', which 
refresh page data
- * from refreshing the current user session
- *
- */
-function getSessionId(sessionKey, value){
-  if (JSON.parse(window.sessionStorage.getItem(sessionKey) === null)) {
-    var storedSession = value;
-    window.sessionStorage.setItem(sessionKey, JSON.stringify(value));
-  } else {
-    storedSession = JSON.parse(window.sessionStorage.getItem(sessionKey));
-  }
-  return storedSession;
-}
-
-
-/**
- * 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.
- */
-
-/**
- * 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)),
-  };
-}
-
-/*
- * 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.
- */
-
-// inherent dependency on globals.js, loaded by the webext
-
-// 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.
-
-var browser = browser || chrome;
-var logs = [];
-var config = {
-  autostart: true,
-  url: 'http://localhost:8000',
-  transmitInterval: 5000,
-  logCountThreshold: 5,
-  userId: null,
-  version: null,
-  resolution: 500,
-  time: timeStampScale({}),
-  on: true,
-};
-var sessionId = 'session_' + Date.now();
-
-var getTimestamp = ((typeof performance !== 'undefined') && (typeof 
performance.now !== 'undefined'))
-  ? function () { return performance.now() + 
performance.timing.navigationStart; }
-  : Date.now;
-
-browser.storage.local.set({ sessionId: sessionId });
-
-var store = browser.storage.local.get({
-  userAleHost: userAleHost,
-  userAleScript: userAleScript,
-  toolUser: toolUser,
-  toolName: toolName,
-  toolVersion: toolVersion,
-}, storeCallback);
-        
-function storeCallback(item) {
-  config = Object.assign({}, config, {
-    url: item.userAleHost,
-    userId: item.toolUser,
-    sessionID: sessionId,
-    toolName: item.toolName,
-    toolVersion: item.toolVersion
-  });
-  initSender(logs, config);
-}
-
-function dispatchTabMessage(message) {
-  browser.tabs.query({}, function (tabs) {
-    tabs.forEach(function (tab) {
-      browser.tabs.sendMessage(tab.id, message);
-    });
-  });
+/*
+ * 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.
+ * @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;
 }
 
-function packageBrowserLog(type, logDetail) {
-  var timeFields = extractTimeFields(getTimestamp());
-
-  logs.push({
-    'target' : null,
-    'path' : null,
-    'clientTime' : timeFields.milli,
-    'microTime' : timeFields.micro,
-    'location' : null,
-    'type' : 'browser.' + type,
-    'logType': 'raw',
-    'userAction' : true,
-    'details' : logDetail,
-    'userId' : toolUser,
-    'toolVersion': null,
-    'toolName': null,
-    'useraleVersion': null,
-    'sessionID': sessionId,
-  });
+/*
+ * 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.
+ */
+
+/**
+ * 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)),
+  };
 }
 
-browser.runtime.onMessage.addListener(function (message) {
-  switch (message.type) {
-    case CONFIG_CHANGE:
-      (function () {
-        var updatedConfig = Object.assign({}, config, {
-          url: message.payload.userAleHost,
-          userId: message.payload.toolUser,
-          toolName: message.payload.toolName,
-          toolVersion: message.payload.toolVersion
-        });
-        initSender(logs, updatedConfig);
-        dispatchTabMessage(message);
-      })();
-      break;
-
-    case ADD_LOG:
-      (function () {
-        logs.push(message.payload);
-      })();
-      break;
-
-    default:
-      console.log('got unknown message type ', message);
-  }
-});
-
-function getTabDetailById(tabId, onReady) {
-  browser.tabs.get(tabId, function (tab) {
-    onReady({
-      active: tab.active,
-      audible: tab.audible,
-      incognito: tab.incognito,
-      index: tab.index,
-      muted: tab.mutedInfo ? tab.mutedInfo.muted : null,
-      pinned: tab.pinned,
-      selected: tab.selected,
-      tabId: tab.id,
-      title: tab.title,
-      url: tab.url,
-      windowId: tab.windowId,
-    });
-  });
+/*
+ * 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);
 }
 
-browser.tabs.onActivated.addListener(function (e) {
-  getTabDetailById(e.tabId, function (detail) {
-    packageBrowserLog('tabs.onActivated', detail);
-  });
-});
-
-browser.tabs.onCreated.addListener(function (tab, e) {
-  packageBrowserLog('tabs.onCreated', {
-    active: tab.active,
-    audible: tab.audible,
-    incognito: tab.incognito,
-    index: tab.index,
-    muted: tab.mutedInfo ? tab.mutedInfo.muted : null,
-    pinned: tab.pinned,
-    selected: tab.selected,
-    tabId: tab.id,
-    title: tab.title,
-    url: tab.url,
-    windowId: tab.windowId,
-  });
-});
-
-browser.tabs.onDetached.addListener(function (tabId) {
-  getTabDetailById(tabId, function (detail) {
-    packageBrowserLog('tabs.onDetached', detail);
-  });
-});
-
-browser.tabs.onMoved.addListener(function (tabId) {
-  getTabDetailById(tabId, function (detail) {
-    packageBrowserLog('tabs.onMoved', detail);
-  });
-});
-
-browser.tabs.onRemoved.addListener(function (tabId) {
-  packageBrowserLog('tabs.onRemoved', { tabId: tabId });
-});
-
-browser.tabs.onZoomChange.addListener(function (e) {
-  getTabDetailById(e.tabId, function (detail) {
-    packageBrowserLog('tabs.onZoomChange', Object.assign({}, {
-      oldZoomFactor: e.oldZoomFactor,
-      newZoomFactor: e.newZoomFactor,
-    }, detail));
-  });
-});
-
-/*
- eslint-enable
+/*
+ * 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.
+ */
+
+// inherent dependency on globals.js, loaded by the webext
+
+// 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.
+
+var browser = browser || chrome;
+var logs = [];
+var config = {
+  autostart: true,
+  url: 'http://localhost:8000',
+  transmitInterval: 5000,
+  logCountThreshold: 5,
+  userId: null,
+  version: null,
+  resolution: 500,
+  time: timeStampScale({}),
+  on: true,
+};
+var sessionId = 'session_' + Date.now();
+
+var getTimestamp = ((typeof performance !== 'undefined') && (typeof 
performance.now !== 'undefined'))
+  ? function () { return performance.now() + 
performance.timing.navigationStart; }
+  : Date.now;
+
+browser.storage.local.set({ sessionId: sessionId });
+
+var store = browser.storage.local.get({
+  userAleHost: userAleHost,
+  userAleScript: userAleScript,
+  toolUser: toolUser,
+  toolName: toolName,
+  toolVersion: toolVersion,
+}, storeCallback);
+        
+function storeCallback(item) {
+  config = Object.assign({}, config, {
+    url: item.userAleHost,
+    userId: item.toolUser,
+    sessionID: sessionId,
+    toolName: item.toolName,
+    toolVersion: item.toolVersion
+  });
+  initSender(logs, config);
+}
+
+function dispatchTabMessage(message) {
+  browser.tabs.query({}, function (tabs) {
+    tabs.forEach(function (tab) {
+      browser.tabs.sendMessage(tab.id, message);
+    });
+  });
+}
+
+function packageBrowserLog(type, logDetail) {
+  var timeFields = extractTimeFields(getTimestamp());
+
+  logs.push({
+    'target' : null,
+    'path' : null,
+    'clientTime' : timeFields.milli,
+    'microTime' : timeFields.micro,
+    'location' : null,
+    'type' : 'browser.' + type,
+    'logType': 'raw',
+    'userAction' : true,
+    'details' : logDetail,
+    'userId' : toolUser,
+    'toolVersion': null,
+    'toolName': null,
+    'useraleVersion': null,
+    'sessionID': sessionId,
+  });
+}
+
+browser.runtime.onMessage.addListener(function (message) {
+  switch (message.type) {
+    case CONFIG_CHANGE:
+      (function () {
+        var updatedConfig = Object.assign({}, config, {
+          url: message.payload.userAleHost,
+          userId: message.payload.toolUser,
+          toolName: message.payload.toolName,
+          toolVersion: message.payload.toolVersion
+        });
+        initSender(logs, updatedConfig);
+        dispatchTabMessage(message);
+      })();
+      break;
+
+    case ADD_LOG:
+      (function () {
+        logs.push(message.payload);
+      })();
+      break;
+
+    default:
+      console.log('got unknown message type ', message);
+  }
+});
+
+function getTabDetailById(tabId, onReady) {
+  browser.tabs.get(tabId, function (tab) {
+    onReady({
+      active: tab.active,
+      audible: tab.audible,
+      incognito: tab.incognito,
+      index: tab.index,
+      muted: tab.mutedInfo ? tab.mutedInfo.muted : null,
+      pinned: tab.pinned,
+      selected: tab.selected,
+      tabId: tab.id,
+      title: tab.title,
+      url: tab.url,
+      windowId: tab.windowId,
+    });
+  });
+}
+
+browser.tabs.onActivated.addListener(function (e) {
+  getTabDetailById(e.tabId, function (detail) {
+    packageBrowserLog('tabs.onActivated', detail);
+  });
+});
+
+browser.tabs.onCreated.addListener(function (tab, e) {
+  packageBrowserLog('tabs.onCreated', {
+    active: tab.active,
+    audible: tab.audible,
+    incognito: tab.incognito,
+    index: tab.index,
+    muted: tab.mutedInfo ? tab.mutedInfo.muted : null,
+    pinned: tab.pinned,
+    selected: tab.selected,
+    tabId: tab.id,
+    title: tab.title,
+    url: tab.url,
+    windowId: tab.windowId,
+  });
+});
+
+browser.tabs.onDetached.addListener(function (tabId) {
+  getTabDetailById(tabId, function (detail) {
+    packageBrowserLog('tabs.onDetached', detail);
+  });
+});
+
+browser.tabs.onMoved.addListener(function (tabId) {
+  getTabDetailById(tabId, function (detail) {
+    packageBrowserLog('tabs.onMoved', detail);
+  });
+});
+
+browser.tabs.onRemoved.addListener(function (tabId) {
+  packageBrowserLog('tabs.onRemoved', { tabId: tabId });
+});
+
+browser.tabs.onZoomChange.addListener(function (e) {
+  getTabDetailById(e.tabId, function (detail) {
+    packageBrowserLog('tabs.onZoomChange', Object.assign({}, {
+      oldZoomFactor: e.oldZoomFactor,
+      newZoomFactor: e.newZoomFactor,
+    }, detail));
+  });
+});
+
+/*
+ eslint-enable
  */
diff --git a/build/UserAleWebExtension/content.js 
b/build/UserAleWebExtension/content.js
index 77ad5e3..19d620f 100644
--- a/build/UserAleWebExtension/content.js
+++ b/build/UserAleWebExtension/content.js
@@ -26,845 +26,848 @@ var toolVersion = '2.0.2';
 
 /* eslint-enable */
 
-/*
-* 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 prefix = 'USERALE_';
-
-var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE';
+/*
+* 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 prefix = 'USERALE_';
+
+var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE';
 var ADD_LOG = prefix + 'ADD_LOG';
 
 var version = "2.0.2";
 
-/*
- * 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 SessionId = getSessionId("userAleSessionId", 'session_' + 
String(Date.now()));
-
-/**
- * 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') || SessionId;
-
-  return settings;
-}
-
-/**
- * defines sessionId, stores it in sessionStorage, checks to see if there is a 
sessionId in
- * storage when script is started. This prevents events like 'submit', which 
refresh page data
- * from refreshing the current user session
- *
- */
-function getSessionId(sessionKey, value){
-  if (JSON.parse(window.sessionStorage.getItem(sessionKey) === null)) {
-    var storedSession = value;
-    window.sessionStorage.setItem(sessionKey, JSON.stringify(value));
-  } else {
-    storedSession = JSON.parse(window.sessionStorage.getItem(sessionKey));
-  }
-  return storedSession;
-}
-
-
-/**
- * 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 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 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);
-}
-
-/**
- * Provides a simplified send function that can be called before events that 
would
- * refresh page can resolve so that log queue ('logs) can be shipped 
immediately. This
- * is different than sendOnClose because browser security practices prevent 
you from
- * listening the process responsible for window navigation actions, in action 
(e.g., refresh;
- * you can only detect, after the fact, the process responsible for the 
current window state.
- * @param  {Array} logs   Array of logs to read from.
- * @param  {Object} config Configuration object to be read from.
- */
-function sendOnRefresh(logs, config) {
-  if (!config.on) {
-    return;
-  }
-  if (logs.length > 0) {
-    sendLogs(logs, config.url, 1);
-  }
-}
-
-/**
- * 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 events;
-var bufferBools;
-var bufferedEvents;
-//@todo: Investigate drag events and their behavior
-var intervalEvents = ['click', 'focus', 'blur', 'input', 'change', 
'mouseover', 'submit'];
-var refreshEvents;
-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
-  };
-
-  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 }; }
-  };
-
-  refreshEvents = {
-    'submit' : null
-  };
-}
-
-/**
- * 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);
-  });
-
-  Object.keys(refreshEvents).forEach(function(ev) {
-    document.addEventListener(ev, function(e) {
-      packageLog(e, events[ev]);
-      sendOnRefresh(logs,config);
-    }, 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 config$1 = {};
-var logs$1 = [];
-var 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 (!started) {
-    setTimeout(function() {
-      var state = document.readyState;
-
-      if (state === 'interactive' || state === 'complete') {
-        attachHandlers(config);
-        initSender(logs$1, config);
-        started = config.on = true;
-      } else {
-        setup(config);
-      }
-    }, 100);
-  }
+/*
+ * 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 sessionId = null;
+
+/**
+ * Extracts the initial configuration settings from the
+ * currently executing script tag.
+ * @return {Object} The extracted configuration object
+ */
+function getInitialSettings() {
+  var settings = {};
+
+  if (sessionId === null) {
+    sessionId = getSessionId('userAleSessionId', 'session_' + 
String(Date.now()));
+  }
+
+  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') || sessionId;
+
+  return settings;
+}
+
+/**
+ * defines sessionId, stores it in sessionStorage, checks to see if there is a 
sessionId in
+ * storage when script is started. This prevents events like 'submit', which 
refresh page data
+ * from refreshing the current user session
+ *
+ */
+function getSessionId(sessionKey, value){
+  if (window.sessionStorage.getItem(sessionKey) === null) {
+    window.sessionStorage.setItem(sessionKey, JSON.stringify(value));
+    return JSON.stringify(value);
+  }
+
+  return JSON.parse(window.sessionStorage.getItem(sessionKey));
+}
+
+
+/**
+ * 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;
 }
 
-/**
- * Used to start the logging process if the
- * autostart configuration option is set to false.
- */
-function start() {
-  if (!started) {
-    setup(config$1);
-  }
-
-  config$1.on = 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.
+ */
+
+/**
+ * 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;
+  }
 }
 
-/**
- * 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;
+/*
+ * 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 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.
- */
-
-// 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.
-
-var browser = browser || chrome;
-
-// creates a Future for retrieval of the named keys
-// the value specified is the default value if one doesn't exist in the storage
-let store = browser.storage.local.get({
-  sessionId: null,
-  userAleHost: userAleHost,
-  userAleScript: userAleScript,
-  toolUser: toolUser,
-  toolName: toolName,
-  toolVersion: toolVersion,
-}, storeCallback);
-        
-function storeCallback(item) {
-  injectScript({
-    url: item.userAleHost,
-    userId: item.toolUser,
-    sessionID: item.sessionId,
-    toolName: item.toolName,
-    toolVersion: item.toolVersion
-  });
+/*
+ * 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);
+}
+
+/**
+ * Provides a simplified send function that can be called before events that 
would
+ * refresh page can resolve so that log queue ('logs) can be shipped 
immediately. This
+ * is different than sendOnClose because browser security practices prevent 
you from
+ * listening the process responsible for window navigation actions, in action 
(e.g., refresh;
+ * you can only detect, after the fact, the process responsible for the 
current window state.
+ * @param  {Array} logs   Array of logs to read from.
+ * @param  {Object} config Configuration object to be read from.
+ */
+function sendOnRefresh(logs, config) {
+  if (!config.on) {
+    return;
+  }
+  if (logs.length > 0) {
+    sendLogs(logs, config.url, 1);
+  }
+}
+
+/**
+ * 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);
 }
 
-function queueLog(log) {
-  browser.runtime.sendMessage({ type: ADD_LOG, payload: 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.
+ */
+
+
+var events;
+var bufferBools;
+var bufferedEvents;
+//@todo: Investigate drag events and their behavior
+var intervalEvents = ['click', 'focus', 'blur', 'input', 'change', 
'mouseover', 'submit'];
+var refreshEvents;
+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
+  };
+
+  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 }; }
+  };
+
+  refreshEvents = {
+    'submit' : null
+  };
+}
+
+/**
+ * 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);
+  });
+
+  Object.keys(refreshEvents).forEach(function(ev) {
+    document.addEventListener(ev, function(e) {
+      packageLog(e, events[ev]);
+      sendOnRefresh(logs,config);
+    }, true);
+  });
+
+  windowEvents.forEach(function(ev) {
+    window.addEventListener(ev, function(e) {
+      packageLog(e, function() { return { 'window' : true }; });
+    }, true);
+  });
+
+  return true;
 }
 
-function injectScript(config) {
-  options(config);
-  start();
-  setLogFilter(function (log) {
-    queueLog(Object.assign({}, log, {
-      pageUrl: document.location.href,
-    }));
-    return false;
-  });
+/*
+ * 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$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 (!started) {
+    setTimeout(function() {
+      var state = document.readyState;
+
+      if (state === 'interactive' || state === 'complete') {
+        attachHandlers(config);
+        initSender(logs$1, config);
+        started = config.on = true;
+      } else {
+        setup(config);
+      }
+    }, 100);
+  }
+}
+
+/**
+ * Used to start the logging process if the
+ * autostart configuration option is set to false.
+ */
+function start() {
+  if (!started) {
+    setup(config$1);
+  }
+
+  config$1.on = true;
+}
+
+/**
+ * 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;
 }
 
-browser.runtime.onMessage.addListener(function (message) {
-  if (message.type === CONFIG_CHANGE) {
-    options({
-      url: message.payload.userAleHost,
-      userId: message.payload.toolUser,
-      toolName: message.payload.toolName,
-      toolVersion: message.payload.toolVersion
-    });
-  }
-});
-
-/*
- eslint-enable
+/*
+ * 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.
+ */
+
+// 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.
+
+var browser = browser || chrome;
+
+// creates a Future for retrieval of the named keys
+// the value specified is the default value if one doesn't exist in the storage
+let store = browser.storage.local.get({
+  sessionId: null,
+  userAleHost: userAleHost,
+  userAleScript: userAleScript,
+  toolUser: toolUser,
+  toolName: toolName,
+  toolVersion: toolVersion,
+}, storeCallback);
+        
+function storeCallback(item) {
+  injectScript({
+    url: item.userAleHost,
+    userId: item.toolUser,
+    sessionID: item.sessionId,
+    toolName: item.toolName,
+    toolVersion: item.toolVersion
+  });
+}
+
+function queueLog(log) {
+  browser.runtime.sendMessage({ type: ADD_LOG, payload: log });
+}
+
+function injectScript(config) {
+  options(config);
+  start();
+  setLogFilter(function (log) {
+    queueLog(Object.assign({}, log, {
+      pageUrl: document.location.href,
+    }));
+    return false;
+  });
+}
+
+browser.runtime.onMessage.addListener(function (message) {
+  if (message.type === CONFIG_CHANGE) {
+    options({
+      url: message.payload.userAleHost,
+      userId: message.payload.toolUser,
+      toolName: message.payload.toolName,
+      toolVersion: message.payload.toolVersion
+    });
+  }
+});
+
+/*
+ eslint-enable
  */
diff --git a/build/UserAleWebExtension/options.js 
b/build/UserAleWebExtension/options.js
index 1badba4..274d183 100644
--- a/build/UserAleWebExtension/options.js
+++ b/build/UserAleWebExtension/options.js
@@ -26,25 +26,25 @@ var toolVersion = '2.0.2';
 
 /* eslint-enable */
 
-/*
-* 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 prefix = 'USERALE_';
-
+/*
+* 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 prefix = 'USERALE_';
+
 var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE';
 
 /*
diff --git a/build/userale-2.0.2.js b/build/userale-2.0.2.js
index 2da29db..44c4c39 100644
--- a/build/userale-2.0.2.js
+++ b/build/userale-2.0.2.js
@@ -26,19 +26,19 @@ var userale = (function (exports) {
    * 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 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,
+   * 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 SessionId = getSessionId("userAleSessionId", 'session_' + 
String(Date.now()));
+   var sessionId = null;
 
   /**
    * Extracts the initial configuration settings from the
@@ -48,6 +48,10 @@ var userale = (function (exports) {
   function getInitialSettings() {
     var settings = {};
 
+    if (sessionId === null) {
+      sessionId = getSessionId('userAleSessionId', 'session_' + 
String(Date.now()));
+    }
+
     var script = document.currentScript || (function () {
       var scripts = document.getElementsByTagName('script');
       return scripts[scripts.length - 1];
@@ -66,7 +70,7 @@ var userale = (function (exports) {
     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') || SessionId;
+    settings.sessionID = get('data-session') || sessionId;
 
     return settings;
   }
@@ -78,13 +82,12 @@ var userale = (function (exports) {
    *
    */
   function getSessionId(sessionKey, value){
-    if (JSON.parse(window.sessionStorage.getItem(sessionKey) === null)) {
-      var storedSession = value;
+    if (window.sessionStorage.getItem(sessionKey) === null) {
       window.sessionStorage.setItem(sessionKey, JSON.stringify(value));
-    } else {
-      storedSession = JSON.parse(window.sessionStorage.getItem(sessionKey));
+      return JSON.stringify(value);
     }
-    return storedSession;
+
+    return JSON.parse(window.sessionStorage.getItem(sessionKey));
   }
 
 
diff --git a/build/userale-2.0.2.min.js b/build/userale-2.0.2.min.js
index 2c46b63..6254ad4 100644
--- a/build/userale-2.0.2.min.js
+++ b/build/userale-2.0.2.min.js
@@ -15,4 +15,4 @@
  * limitations under the License.
  * @preserved
  */
-var userale=function(n){"use strict";var 
a,i,u,l,s,c,d,f,e="2.0.2",t=function(e,t){if(JSON.parse(null===window.sessionStorage.getItem(e))){var
 n=t;window.sessionStorage.setItem(e,JSON.stringify(t))}else 
n=JSON.parse(window.sessionStorage.getItem(e));return 
n}("userAleSessionId","session_"+String(Date.now()));function 
o(n,o){Object.keys(o).forEach(function(e){if("userFromParams"===e){var 
t=function(e){var t=new 
RegExp("[?&]"+e+"(=([^&#]*)|&|#|$)"),n=window.location.href.match(t);return n& 
[...]
\ No newline at end of file
+var userale=function(n){"use strict";var 
a,i,u,l,s,c,d,f,t="2.0.2",r=null;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 m=null,p=null;function 
o(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
diff --git a/example/index.html b/example/index.html
index b5c90a4..8c8cde3 100644
--- a/example/index.html
+++ b/example/index.html
@@ -18,13 +18,14 @@ limitations under the License.
 <head>
   <title>UserAleJS - Example Page</title>
   <script
-    
src="file:///Users/jpoore/Documents/Apache_Flagon/test/incubator-flagon-useralejs/build/userale-2.0.2.min.js"
+    src="file:///{your file 
path}/incubator-flagon-useralejs/build/userale-2.0.2.min.js"
     data-url="http://localhost:8000/";
     data-user="example-user"
+    data-log-details="true"
     data-version="2.0.2"
     data-tool="Apache UserALE.js Example"
   ></script>
-<!--Try out the options function to change UserALE.js params@
+<!--Try out the options function to change UserALE.js params!
   <script type="text/javascript">
       const changeMe = "me";
       window.userale.options({
@@ -34,17 +35,21 @@ limitations under the License.
       })
   </script>
 -->
-<!--Try out a UserALE.js filter!
-    <script type="text/javascript">
-    window.userale.filter(function (log) {
-      var type_array = ['mouseup', 'mouseover', 'dblclick', 'blur', 'focus'];
-      var logType_array = ['interval'];
-      return !type_array.includes(log.type) && 
!logType_array.includes(log.logType);
-    });
+<!--Try out a UserALE.js filter to eliminate the logs that you don't want!
+  <script type="text/javascript">
+  window.userale.filter(function (log) {
+    var type_array = ['mouseup', 'mouseover', 'dblclick', 'blur', 'focus'];
+    var logType_array = ['interval'];
+    return !type_array.includes(log.type) && 
!logType_array.includes(log.logType);
+  });
   </script>
 -->
 </head>
 <body>
+  <br>
+  <br>
+  <br>
+  <p>Click the button below to generate targeted click events.</p>
   <div class="container">
     <button id="test_button">Click me!</button>
   </div>
@@ -55,10 +60,38 @@ limitations under the License.
         if (targetsForLabels.includes(log.target)) {
             return Object.assign({}, log, { CustomLabel: "Click me!" });
         } else {
-            return log;  
-        } 
+            return log;
+        }
       });
   </script>
 -->
+  <br>
+  <br>
+  <br>
+  <p>Play around with these form elements to see a variety of UserALE.js log 
types:</p>
+  <p>Capture inputs and changes to text fields</p>
+  <form id="test_text_input">
+    <label>Test field: <input type="text"></label>
+    <br><br>
+    <button type="submit">Submit form</button>
+  </form>
+  <br>
+  <br>
+  <br>
+  <p>Capture inputs and changes to different types of selections</p>
+  <select name="UserALE.js Committers">
+    <option value="clay">Clay</option>
+    <option value="rob">Rob</option>
+    <option value="michelle">Michelle</option>
+    <option value="josh">Josh</option>
+  </select>
+  <br>
+  <br>
+  <br>
+  <form id="test_radio_input">
+    <input type="radio" name="Use Case" value="business analytics" checked> 
Business Analytics<br>
+    <input type="radio" name="Use Case" value="HCI"> HCI<br>
+    <input type="radio" name="Use Case" value="other"> Other
+  </form>
 </body>
 </html>
diff --git a/example/index_form.html b/example/index_form.html
deleted file mode 100644
index 55977fc..0000000
--- a/example/index_form.html
+++ /dev/null
@@ -1,93 +0,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.
--->
-<html>
-<head>
-  <title>UserAleJS - Example Page</title>
-  <script
-    
src="file:///Users/jpoore/Documents/Apache_Flagon/test/incubator-flagon-useralejs/build/userale-2.0.2.min.js"
-    data-url="http://localhost:8000/";
-    data-user="example-user"
-    data-log-details="true"
-    data-version="2.0.2"
-    data-tool="Apache UserALE.js Example"
-  ></script>
-    <script type="text/javascript">
-        window.userale.filter(function (log) {
-            var type_array = ['mouseup', 'mousedown', 'dblclick', 'blur'];
-            var logType_array = ['interval'];
-            return !type_array.includes(log.type) && 
!logType_array.includes(log.logType);
-        });
-    </script>
-
-</head>
-<body>
-
-<!---
-<script type="text/javascript">
-    const changeMe = "me";
-    window.userale.options({
-      "userId": changeMe,
-      "version": "4.0.2",
-      "sessionID": "4"
-    })
-</script>
--->
-  <br>
-  <br>
-  <br>
-  <div class="container">
-    <button id="test_button">Click me!</button>
-  </div>
-<!--Play around with this mapping function to transform your logs!
-    <script type="text/javascript">
-      window.userale.map(function (log) {
-        var targetsForLabels = ["button#test_button"];
-        if (targetsForLabels.includes(log.target)) {
-            return Object.assign({}, log, { CustomLabel: "Click me!" });
-        } else {
-            return log;  
-        } 
-      });
-  </script>
--->
-  <br>
-  <br>
-  <br>
-  <form id="test_text_input">
-    <label>Test field: <input type="text"></label>
-    <br><br>
-    <button type="submit">Submit form</button>
-  </form>
-  <br>
-  <br>
-  <br>
-  <select name="cars">
-    <option value="volvo">Volvo</option>
-    <option value="saab">Saab</option>
-    <option value="fiat">Fiat</option>
-    <option value="audi">Audi</option>
-  </select>
-  <br>
-  <br>
-  <br>
-  <form id="test_radio_input">
-    <input type="radio" name="gender" value="male" checked> Male<br>
-    <input type="radio" name="gender" value="female"> Female<br>
-    <input type="radio" name="gender" value="other"> Other
-  </form>
-</body>
-</html>
diff --git a/test/testUtils.js b/test/testUtils.js
index ece0651..af8cf81 100644
--- a/test/testUtils.js
+++ b/test/testUtils.js
@@ -58,6 +58,8 @@ export function createEnv(html, doneCallback, extraConfig) {
       }
 
       window.sessionStorage = new Storage(null, { strict: true });
+      window.localStorage = new Storage(null, { strict: true });
+
     },
     done: doneCallback,
     virtualConsole,

Reply via email to