You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flagon.apache.org by po...@apache.org on 2019/08/09 18:48:59 UTC

[incubator-flagon-useralejs] branch FLAGON-434 created (now 397a22b)

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

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


      at 397a22b  [FLAGON-440] created sendOnRefresh function to call via attachEventListeners so data isn't lost for events that trigger refreshes

This branch includes the following new commits:

     new 397a22b  [FLAGON-440] created sendOnRefresh function to call via attachEventListeners so data isn't lost for events that trigger refreshes

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[incubator-flagon-useralejs] 01/01: [FLAGON-440] created sendOnRefresh function to call via attachEventListeners so data isn't lost for events that trigger refreshes

Posted by po...@apache.org.
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

commit 397a22b46b52b0c732fae8bf1e8652fab863530d
Author: poorejc <po...@apache.org>
AuthorDate: Fri Aug 9 14:48:39 2019 -0400

    [FLAGON-440] created sendOnRefresh function to call via attachEventListeners so data isn't lost for events that trigger refreshes
---
 build/UserAleWebExtension/content.js | 238 ++++++++++++++++++++---------------
 build/userale-2.0.2.js               | 238 ++++++++++++++++++++---------------
 build/userale-2.0.2.min.js           |   2 +-
 example/index_form.html              |  81 ++++++++++++
 src/attachHandlers.js                |  20 ++-
 src/sendLogs.js                      |  18 +++
 6 files changed, 385 insertions(+), 212 deletions(-)

diff --git a/build/UserAleWebExtension/content.js b/build/UserAleWebExtension/content.js
index ab02b35..9efa490 100644
--- a/build/UserAleWebExtension/content.js
+++ b/build/UserAleWebExtension/content.js
@@ -468,11 +468,133 @@ function selectorizePath(path) {
  * 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'];
 
 /**
@@ -512,8 +634,7 @@ function defineDetails(config) {
     'drag' : null,
     'drop' : null,
     'keydown' : config.logDetails ? function(e) { return { 'key' : e.keyCode, 'ctrl' : e.ctrlKey, 'alt' : e.altKey, 'shift' : e.shiftKey, 'meta' : e.metaKey }; } : null,
-    'mouseover' : null,
-    'submit' : null
+    'mouseover' : null
   };
 
   bufferBools = {};
@@ -522,6 +643,10 @@ function defineDetails(config) {
     'scroll' : function() { return { 'x' : window.scrollX, 'y' : window.scrollY }; },
     'resize' : function() { return { 'width' : window.outerWidth, 'height' : window.outerHeight }; }
   };
+
+  refreshEvents = {
+    'submit' : null
+  };
 }
 
 /**
@@ -556,6 +681,13 @@ function attachHandlers(config) {
     }, 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 }; });
@@ -582,108 +714,6 @@ function attachHandlers(config) {
  * limitations under the License.
  */
 
-var sendIntervalId = null;
-
-/**
- * Initializes the log queue processors.
- * @param  {Array} logs   Array of logs to append to.
- * @param  {Object} config Configuration object to use when logging.
- */
-function initSender(logs, config) {
-  if (sendIntervalId !== null) {
-    clearInterval(sendIntervalId);
-  }
-
-  sendIntervalId = sendOnInterval(logs, config);
-  sendOnClose(logs, config);
-}
-
-/**
- * Checks the provided log array on an interval, flushing the logs
- * if the queue has reached the threshold specified by the provided config.
- * @param  {Array} logs   Array of logs to read from.
- * @param  {Object} config Configuration object to be read from.
- * @return {Number}        The newly created interval id.
- */
-function sendOnInterval(logs, config) {
-  return setInterval(function() {
-    if (!config.on) {
-      return;
-    }
-
-    if (logs.length >= config.logCountThreshold) {
-      sendLogs(logs.slice(0), config.url, 0); // Send a copy
-      logs.splice(0); // Clear array reference (no reassignment)
-    }
-  }, config.transmitInterval);
-}
-
-/**
- * Attempts to flush the remaining logs when the window is closed.
- * @param  {Array} logs   Array of logs to be flushed.
- * @param  {Object} config Configuration object to be read from.
- */
-function sendOnClose(logs, config) {
-  if (!config.on) {
-    return;
-  }
-
-  if (navigator.sendBeacon) {
-    window.addEventListener('unload', function() {
-      navigator.sendBeacon(config.url, JSON.stringify(logs));
-    });
-  } else {
-    window.addEventListener('beforeunload', function() {
-      if (logs.length > 0) {
-        sendLogs(logs, config.url, 1);
-      }
-    });
-  }
-}
-
-/**
- * Sends the provided array of logs to the specified url,
- * retrying the request up to the specified number of retries.
- * @param  {Array} logs    Array of logs to send.
- * @param  {string} url     URL to send the POST request to.
- * @param  {Number} retries Maximum number of attempts to send the logs.
- */
-function sendLogs(logs, url, retries) {
-  var req = new XMLHttpRequest();
-
-  var data = JSON.stringify(logs);
-
-  req.open('POST', url);
-  req.setRequestHeader('Content-type', 'application/json;charset=UTF-8');
-
-  req.onreadystatechange = function() {
-    if (req.readyState === 4 && req.status !== 200) {
-      if (retries > 0) {
-        sendLogs(logs, url, retries--);
-      }
-    }
-  };
-
-  req.send(data);
-}
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- * 
- *   http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
 var config$1 = {};
 var logs$1 = [];
 var started = false;
diff --git a/build/userale-2.0.2.js b/build/userale-2.0.2.js
index 28bf257..f0ba930 100644
--- a/build/userale-2.0.2.js
+++ b/build/userale-2.0.2.js
@@ -447,11 +447,133 @@ var userale = (function (exports) {
    * 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'];
 
   /**
@@ -491,8 +613,7 @@ var userale = (function (exports) {
       'drag' : null,
       'drop' : null,
       'keydown' : config.logDetails ? function(e) { return { 'key' : e.keyCode, 'ctrl' : e.ctrlKey, 'alt' : e.altKey, 'shift' : e.shiftKey, 'meta' : e.metaKey }; } : null,
-      'mouseover' : null,
-      'submit' : null
+      'mouseover' : null
     };
 
     bufferBools = {};
@@ -501,6 +622,10 @@ var userale = (function (exports) {
       'scroll' : function() { return { 'x' : window.scrollX, 'y' : window.scrollY }; },
       'resize' : function() { return { 'width' : window.outerWidth, 'height' : window.outerHeight }; }
     };
+
+    refreshEvents = {
+      'submit' : null
+    };
   }
 
   /**
@@ -535,6 +660,13 @@ var userale = (function (exports) {
       }, 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 }; });
@@ -561,108 +693,6 @@ var userale = (function (exports) {
    * limitations under the License.
    */
 
-  var sendIntervalId = null;
-
-  /**
-   * Initializes the log queue processors.
-   * @param  {Array} logs   Array of logs to append to.
-   * @param  {Object} config Configuration object to use when logging.
-   */
-  function initSender(logs, config) {
-    if (sendIntervalId !== null) {
-      clearInterval(sendIntervalId);
-    }
-
-    sendIntervalId = sendOnInterval(logs, config);
-    sendOnClose(logs, config);
-  }
-
-  /**
-   * Checks the provided log array on an interval, flushing the logs
-   * if the queue has reached the threshold specified by the provided config.
-   * @param  {Array} logs   Array of logs to read from.
-   * @param  {Object} config Configuration object to be read from.
-   * @return {Number}        The newly created interval id.
-   */
-  function sendOnInterval(logs, config) {
-    return setInterval(function() {
-      if (!config.on) {
-        return;
-      }
-
-      if (logs.length >= config.logCountThreshold) {
-        sendLogs(logs.slice(0), config.url, 0); // Send a copy
-        logs.splice(0); // Clear array reference (no reassignment)
-      }
-    }, config.transmitInterval);
-  }
-
-  /**
-   * Attempts to flush the remaining logs when the window is closed.
-   * @param  {Array} logs   Array of logs to be flushed.
-   * @param  {Object} config Configuration object to be read from.
-   */
-  function sendOnClose(logs, config) {
-    if (!config.on) {
-      return;
-    }
-
-    if (navigator.sendBeacon) {
-      window.addEventListener('unload', function() {
-        navigator.sendBeacon(config.url, JSON.stringify(logs));
-      });
-    } else {
-      window.addEventListener('beforeunload', function() {
-        if (logs.length > 0) {
-          sendLogs(logs, config.url, 1);
-        }
-      });
-    }
-  }
-
-  /**
-   * Sends the provided array of logs to the specified url,
-   * retrying the request up to the specified number of retries.
-   * @param  {Array} logs    Array of logs to send.
-   * @param  {string} url     URL to send the POST request to.
-   * @param  {Number} retries Maximum number of attempts to send the logs.
-   */
-  function sendLogs(logs, url, retries) {
-    var req = new XMLHttpRequest();
-
-    var data = JSON.stringify(logs);
-
-    req.open('POST', url);
-    req.setRequestHeader('Content-type', 'application/json;charset=UTF-8');
-
-    req.onreadystatechange = function() {
-      if (req.readyState === 4 && req.status !== 200) {
-        if (retries > 0) {
-          sendLogs(logs, url, retries--);
-        }
-      }
-    };
-
-    req.send(data);
-  }
-
-  /*
-   * Licensed to the Apache Software Foundation (ASF) under one or more
-   * contributor license agreements.  See the NOTICE file distributed with
-   * this work for additional information regarding copyright ownership.
-   * The ASF licenses this file to You under the Apache License, Version 2.0
-   * (the "License"); you may not use this file except in compliance with
-   * the License.  You may obtain a copy of the License at
-   * 
-   *   http://www.apache.org/licenses/LICENSE-2.0
-   * 
-   * Unless required by applicable law or agreed to in writing, software
-   * distributed under the License is distributed on an "AS IS" BASIS,
-   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   * See the License for the specific language governing permissions and
-   * limitations under the License.
-   */
-
   var config$1 = {};
   var logs$1 = [];
   exports.started = false;
diff --git a/build/userale-2.0.2.min.js b/build/userale-2.0.2.min.js
index 85b781b..0a3b0c8 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,t="2.0.2";function e(n,o){Object.keys(o).forEach(function(t){if("userFromParams"===t){var e=function(t){var e=new RegExp("[?&]"+t+"(=([^&#]*)|&|#|$)"),n=window.location.href.match(e);return n&&n[2]?decodeURIComponent(n[2].replace(/\+/g," ")):null}(o[t]);e&&(n.userId=e)}n[t]=o[t]})}var o,r,m,p=null,g=null;function v(t,e){if(!i.on)return!1;var n=null;e&&(n=e(t));var o=function(t){return{milli:Math.floor(t),micro:Number((t%1).toFixed( [...]
\ No newline at end of file
+var userale=function(n){"use strict";var a,i,u,l,s,c,d,f,t="2.0.2";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(3))}}( [...]
\ No newline at end of file
diff --git a/example/index_form.html b/example/index_form.html
new file mode 100644
index 0000000..7095e3d
--- /dev/null
+++ b/example/index_form.html
@@ -0,0 +1,81 @@
+<!--
+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>
+  <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/src/attachHandlers.js b/src/attachHandlers.js
index f025a4e..43b8aba 100644
--- a/src/attachHandlers.js
+++ b/src/attachHandlers.js
@@ -15,14 +15,18 @@
  * limitations under the License.
  */
 
+import { logs } from './packageLogs';
 import { packageLog } from './packageLogs.js';
-import { packageIntervalLog } from './packageLogs';
+import { packageIntervalLog} from './packageLogs';
+import { sendOnRefresh } from "./sendLogs";
+
 
 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'];
 
 /**
@@ -62,8 +66,7 @@ export function defineDetails(config) {
     'drag' : null,
     'drop' : null,
     'keydown' : config.logDetails ? function(e) { return { 'key' : e.keyCode, 'ctrl' : e.ctrlKey, 'alt' : e.altKey, 'shift' : e.shiftKey, 'meta' : e.metaKey }; } : null,
-    'mouseover' : null,
-    'submit' : null
+    'mouseover' : null
   };
 
   bufferBools = {};
@@ -72,6 +75,10 @@ export function defineDetails(config) {
     'scroll' : function() { return { 'x' : window.scrollX, 'y' : window.scrollY }; },
     'resize' : function() { return { 'width' : window.outerWidth, 'height' : window.outerHeight }; }
   };
+
+  refreshEvents = {
+    'submit' : null
+  };
 }
 
 /**
@@ -106,6 +113,13 @@ export function attachHandlers(config) {
     }, 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 }; });
diff --git a/src/sendLogs.js b/src/sendLogs.js
index 8f1bc86..b057a3e 100644
--- a/src/sendLogs.js
+++ b/src/sendLogs.js
@@ -52,6 +52,24 @@ export function sendOnInterval(logs, config) {
 }
 
 /**
+ * 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.
+ */
+export 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.