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:49:00 UTC

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

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.