Merge pull request #401 from EandrewJones/#400-Allow-users-to-add-custom-headers

Allow users to add custom headers
diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js
index 845cad6..c8b4808 100644
--- a/build/UserALEWebExtension/background.js
+++ b/build/UserALEWebExtension/background.js
@@ -340,22 +340,60 @@
   };
 }
 
-/*
- * 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.
- */
+function _arrayWithHoles(arr) {
+  if (Array.isArray(arr)) return arr;
+}
+
+function _iterableToArrayLimit(r, l) {
+  var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
+  if (null != t) {
+    var e,
+      n,
+      i,
+      u,
+      a = [],
+      f = !0,
+      o = !1;
+    try {
+      if (i = (t = t.call(r)).next, 0 === l) {
+        if (Object(t) !== t) return;
+        f = !1;
+      } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
+    } catch (r) {
+      o = !0, n = r;
+    } finally {
+      try {
+        if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return;
+      } finally {
+        if (o) throw n;
+      }
+    }
+    return a;
+  }
+}
+
+function _arrayLikeToArray(arr, len) {
+  if (len == null || len > arr.length) len = arr.length;
+  for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
+  return arr2;
+}
+
+function _unsupportedIterableToArray(o, minLen) {
+  if (!o) return;
+  if (typeof o === "string") return _arrayLikeToArray(o, minLen);
+  var n = Object.prototype.toString.call(o).slice(8, -1);
+  if (n === "Object" && o.constructor) n = o.constructor.name;
+  if (n === "Map" || n === "Set") return Array.from(o);
+  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
+}
+
+function _nonIterableRest() {
+  throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
+}
+
+function _slicedToArray(arr, i) {
+  return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
+}
 
 var sendIntervalId = null;
 
@@ -427,6 +465,14 @@
     req.setRequestHeader("Authorization", config.authHeader);
   }
   req.setRequestHeader("Content-type", "application/json;charset=UTF-8");
+  if (config.headers) {
+    Object.entries(config.headers).forEach(function (_ref) {
+      var _ref2 = _slicedToArray(_ref, 2),
+        header = _ref2[0],
+        value = _ref2[1];
+      req.setRequestHeader(header, value);
+    });
+  }
   req.onreadystatechange = function () {
     if (req.readyState === 4 && req.status !== 200) {
       if (retries > 0) {
diff --git a/build/UserALEWebExtension/content.js b/build/UserALEWebExtension/content.js
index d3dbf9b..7211153 100644
--- a/build/UserALEWebExtension/content.js
+++ b/build/UserALEWebExtension/content.js
@@ -99,6 +99,7 @@
   settings.sessionID = get('data-session') || sessionId;
   settings.authHeader = get('data-auth') || null;
   settings.custIndex = get('data-index') || null;
+  settings.headers = get('data-headers') || null;
   return settings;
 }
 
@@ -906,22 +907,60 @@
   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.
- */
+function _arrayWithHoles(arr) {
+  if (Array.isArray(arr)) return arr;
+}
+
+function _iterableToArrayLimit(r, l) {
+  var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
+  if (null != t) {
+    var e,
+      n,
+      i,
+      u,
+      a = [],
+      f = !0,
+      o = !1;
+    try {
+      if (i = (t = t.call(r)).next, 0 === l) {
+        if (Object(t) !== t) return;
+        f = !1;
+      } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
+    } catch (r) {
+      o = !0, n = r;
+    } finally {
+      try {
+        if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return;
+      } finally {
+        if (o) throw n;
+      }
+    }
+    return a;
+  }
+}
+
+function _arrayLikeToArray(arr, len) {
+  if (len == null || len > arr.length) len = arr.length;
+  for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
+  return arr2;
+}
+
+function _unsupportedIterableToArray(o, minLen) {
+  if (!o) return;
+  if (typeof o === "string") return _arrayLikeToArray(o, minLen);
+  var n = Object.prototype.toString.call(o).slice(8, -1);
+  if (n === "Object" && o.constructor) n = o.constructor.name;
+  if (n === "Map" || n === "Set") return Array.from(o);
+  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
+}
+
+function _nonIterableRest() {
+  throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
+}
+
+function _slicedToArray(arr, i) {
+  return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
+}
 
 var sendIntervalId = null;
 
@@ -993,6 +1032,14 @@
     req.setRequestHeader("Authorization", config.authHeader);
   }
   req.setRequestHeader("Content-type", "application/json;charset=UTF-8");
+  if (config.headers) {
+    Object.entries(config.headers).forEach(function (_ref) {
+      var _ref2 = _slicedToArray(_ref, 2),
+        header = _ref2[0],
+        value = _ref2[1];
+      req.setRequestHeader(header, value);
+    });
+  }
   req.onreadystatechange = function () {
     if (req.readyState === 4 && req.status !== 200) {
       if (retries > 0) {
diff --git a/build/userale-2.4.0.js b/build/userale-2.4.0.js
index 4876a80..be9f847 100644
--- a/build/userale-2.4.0.js
+++ b/build/userale-2.4.0.js
@@ -84,6 +84,7 @@
     settings.sessionID = get('data-session') || sessionId;
     settings.authHeader = get('data-auth') || null;
     settings.custIndex = get('data-index') || null;
+    settings.headers = get('data-headers') || null;
     return settings;
   }
 
@@ -968,6 +969,61 @@
     return true;
   }
 
+  function _arrayWithHoles(arr) {
+    if (Array.isArray(arr)) return arr;
+  }
+
+  function _iterableToArrayLimit(r, l) {
+    var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
+    if (null != t) {
+      var e,
+        n,
+        i,
+        u,
+        a = [],
+        f = !0,
+        o = !1;
+      try {
+        if (i = (t = t.call(r)).next, 0 === l) {
+          if (Object(t) !== t) return;
+          f = !1;
+        } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
+      } catch (r) {
+        o = !0, n = r;
+      } finally {
+        try {
+          if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return;
+        } finally {
+          if (o) throw n;
+        }
+      }
+      return a;
+    }
+  }
+
+  function _arrayLikeToArray(arr, len) {
+    if (len == null || len > arr.length) len = arr.length;
+    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
+    return arr2;
+  }
+
+  function _unsupportedIterableToArray(o, minLen) {
+    if (!o) return;
+    if (typeof o === "string") return _arrayLikeToArray(o, minLen);
+    var n = Object.prototype.toString.call(o).slice(8, -1);
+    if (n === "Object" && o.constructor) n = o.constructor.name;
+    if (n === "Map" || n === "Set") return Array.from(o);
+    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
+  }
+
+  function _nonIterableRest() {
+    throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
+  }
+
+  function _slicedToArray(arr, i) {
+    return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
+  }
+
   /*
    * Licensed to the Apache Software Foundation (ASF) under one or more
    * contributor license agreements.  See the NOTICE file distributed with
@@ -1038,23 +1094,6 @@
     }
   }
 
-  /*
-   * 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;
 
   /**
@@ -1128,6 +1167,14 @@
       req.setRequestHeader("Authorization", config.authHeader);
     }
     req.setRequestHeader("Content-type", "application/json;charset=UTF-8");
+    if (config.headers) {
+      Object.entries(config.headers).forEach(function (_ref) {
+        var _ref2 = _slicedToArray(_ref, 2),
+          header = _ref2[0],
+          value = _ref2[1];
+        req.setRequestHeader(header, value);
+      });
+    }
     req.onreadystatechange = function () {
       if (req.readyState === 4 && req.status !== 200) {
         if (retries > 0) {
diff --git a/build/userale-2.4.0.min.js b/build/userale-2.4.0.min.js
index 6d4277b..1fa96eb 100644
--- a/build/userale-2.4.0.min.js
+++ b/build/userale-2.4.0.min.js
@@ -15,4 +15,4 @@
  * limitations under the License.
  * @preserved
  */
-!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).userale={})}(this,(function(e){"use strict";function t(e){return t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t(e)}var n="2.4.0",o=null;function r(e,t){var n=e.autostart,o=t.autostart;Object.keys(t).forEach((function(n){if("userFromParams"===n){var o=(r=t[n],i=new RegExp("[?&]"+r+"(=([^&#]*)|&|#|$)"),(a=window.location.href.match(i))&&a[2]?decodeURIComponent(a[2].replace(/\+/g," ")):null);o&&(e.userId=o)}var r,i,a;e[n]=t[n]})),!1!==n&&!1!==o||(e.autostart=!1)}var i=function(e,t,n){if(n||2===arguments.length)for(var o,r=0,i=t.length;r<i;r++)!o&&r in t||(o||(o=Array.prototype.slice.call(t,0,r)),o[r]=t[r]);return e.concat(o||Array.prototype.slice.call(t))},a=function(e,t,n){this.name=e,this.version=t,this.os=n,this.type="browser"},s=function(e){this.version=e,this.type="node",this.name="node",this.os=process.platform},u=function(e,t,n,o){this.name=e,this.version=t,this.os=n,this.bot=o,this.type="bot-device"},l=function(){this.type="bot",this.bot=!0,this.name="bot",this.version=null,this.os=null},c=function(){this.type="react-native",this.name="react-native",this.version=null,this.os=null},d=/(nuhk|curl|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask\ Jeeves\/Teoma|ia_archiver)/,f=3,m=[["aol",/AOLShield\/([0-9\._]+)/],["edge",/Edge\/([0-9\._]+)/],["edge-ios",/EdgiOS\/([0-9\._]+)/],["yandexbrowser",/YaBrowser\/([0-9\._]+)/],["kakaotalk",/KAKAOTALK\s([0-9\.]+)/],["samsung",/SamsungBrowser\/([0-9\.]+)/],["silk",/\bSilk\/([0-9._-]+)\b/],["miui",/MiuiBrowser\/([0-9\.]+)$/],["beaker",/BeakerBrowser\/([0-9\.]+)/],["edge-chromium",/EdgA?\/([0-9\.]+)/],["chromium-webview",/(?!Chrom.*OPR)wv\).*Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/],["chrome",/(?!Chrom.*OPR)Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/],["phantomjs",/PhantomJS\/([0-9\.]+)(:?\s|$)/],["crios",/CriOS\/([0-9\.]+)(:?\s|$)/],["firefox",/Firefox\/([0-9\.]+)(?:\s|$)/],["fxios",/FxiOS\/([0-9\.]+)/],["opera-mini",/Opera Mini.*Version\/([0-9\.]+)/],["opera",/Opera\/([0-9\.]+)(?:\s|$)/],["opera",/OPR\/([0-9\.]+)(:?\s|$)/],["pie",/^Microsoft Pocket Internet Explorer\/(\d+\.\d+)$/],["pie",/^Mozilla\/\d\.\d+\s\(compatible;\s(?:MSP?IE|MSInternet Explorer) (\d+\.\d+);.*Windows CE.*\)$/],["netfront",/^Mozilla\/\d\.\d+.*NetFront\/(\d.\d)/],["ie",/Trident\/7\.0.*rv\:([0-9\.]+).*\).*Gecko$/],["ie",/MSIE\s([0-9\.]+);.*Trident\/[4-7].0/],["ie",/MSIE\s(7\.0)/],["bb10",/BB10;\sTouch.*Version\/([0-9\.]+)/],["android",/Android\s([0-9\.]+)/],["ios",/Version\/([0-9\._]+).*Mobile.*Safari.*/],["safari",/Version\/([0-9\._]+).*Safari/],["facebook",/FB[AS]V\/([0-9\.]+)/],["instagram",/Instagram\s([0-9\.]+)/],["ios-webview",/AppleWebKit\/([0-9\.]+).*Mobile/],["ios-webview",/AppleWebKit\/([0-9\.]+).*Gecko\)$/],["curl",/^curl\/([0-9\.]+)$/],["searchbot",/alexa|bot|crawl(er|ing)|facebookexternalhit|feedburner|google web preview|nagios|postrank|pingdom|slurp|spider|yahoo!|yandex/]],p=[["iOS",/iP(hone|od|ad)/],["Android OS",/Android/],["BlackBerry OS",/BlackBerry|BB10/],["Windows Mobile",/IEMobile/],["Amazon OS",/Kindle/],["Windows 3.11",/Win16/],["Windows 95",/(Windows 95)|(Win95)|(Windows_95)/],["Windows 98",/(Windows 98)|(Win98)/],["Windows 2000",/(Windows NT 5.0)|(Windows 2000)/],["Windows XP",/(Windows NT 5.1)|(Windows XP)/],["Windows Server 2003",/(Windows NT 5.2)/],["Windows Vista",/(Windows NT 6.0)/],["Windows 7",/(Windows NT 6.1)/],["Windows 8",/(Windows NT 6.2)/],["Windows 8.1",/(Windows NT 6.3)/],["Windows 10",/(Windows NT 10.0)/],["Windows ME",/Windows ME/],["Windows CE",/Windows CE|WinCE|Microsoft Pocket Internet Explorer/],["Open BSD",/OpenBSD/],["Sun OS",/SunOS/],["Chrome OS",/CrOS/],["Linux",/(Linux)|(X11)/],["Mac OS",/(Mac_PowerPC)|(Macintosh)/],["QNX",/QNX/],["BeOS",/BeOS/],["OS/2",/OS\/2/]];function h(e){var t=function(e){return""!==e&&m.reduce((function(t,n){var o=n[0],r=n[1];if(t)return t;var i=r.exec(e);return!!i&&[o,i]}),!1)}(e);if(!t)return null;var n=t[0],o=t[1];if("searchbot"===n)return new l;var r=o[1]&&o[1].split(".").join("_").split("_").slice(0,3);r?r.length<f&&(r=i(i([],r,!0),function(e){for(var t=[],n=0;n<e;n++)t.push("0");return t}(f-r.length),!0)):r=[];var s=r.join("."),c=function(e){for(var t=0,n=p.length;t<n;t++){var o=p[t],r=o[0];if(o[1].exec(e))return r}return null}(e),h=d.exec(e);return h&&h[1]?new u(n,s,c,h[1]):new a(n,s,c)}var w,g,v,y,b,S,O,k,E,W,T,I,N=w?h(w):"undefined"==typeof document&&"undefined"!=typeof navigator&&"ReactNative"===navigator.product?new c:"undefined"!=typeof navigator?h(navigator.userAgent):"undefined"!=typeof process&&process.version?new s(process.version.slice(1)):null,x={};function D(e,t){if(!v.on)return!1;var n=null;t&&(n=t(e));for(var o,r=(o=e.timeStamp&&e.timeStamp>0?v.time(e.timeStamp):Date.now(),{milli:Math.floor(o),micro:Number((o%1).toFixed(3))}),i={target:B(e.target),path:P(e),pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:j(),clientTime:r.milli,microTime:r.micro,location:A(e),scrnRes:M(),type:e.type,logType:"raw",userAction:!0,details:n,userId:v.userId,toolVersion:v.version,toolName:v.toolName,useraleVersion:v.useraleVersion,sessionID:v.sessionID},a=0,s=Object.values(x);a<s.length;a++){var u=s[a];if("function"==typeof u&&!(i=u(i,e)))return!1}return g.push(i),!0}function C(e,t,n){if(!v.on)return!1;var o=null;t&&(o=t());for(var r={pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:j(),clientTime:Date.now(),scrnRes:M(),logType:"custom",userAction:n,details:o,userId:v.userId,toolVersion:v.version,toolName:v.toolName,useraleVersion:v.useraleVersion,sessionID:v.sessionID},i=Object.assign(r,e),a=0,s=Object.values(x);a<s.length;a++){var u=s[a];if("function"==typeof u&&!(i=u(i,null)))return!1}return g.push(i),!0}function A(e){return null!=e.pageX?{x:e.pageX,y:e.pageY}:null!=e.clientX?{x:document.documentElement.scrollLeft+e.clientX,y:document.documentElement.scrollTop+e.clientY}:{x:null,y:null}}function M(){return{width:window.innerWidth,height:window.innerHeight}}function B(e){return e.localName?e.localName+(e.id?"#"+e.id:"")+(e.className?"."+e.className:""):e.nodeName?e.nodeName+(e.id?"#"+e.id:"")+(e.className?"."+e.className:""):e&&e.document&&e.location&&e.alert&&e.setInterval?"Window":"Unknown"}function P(e){if(e instanceof window.Event)return function(e){var t,n=0,o=[];for(;t=e[n];)o.push(B(t)),++n;return o}(e.composedPath())}function j(){return{browser:N?N.name:"",version:N?N.version:""}}var K,V=["click","focus","blur","input","change","mouseover","submit"],L=["load","blur","focus"];function R(e){return{clicks:e.detail,ctrl:e.ctrlKey,alt:e.altKey,shift:e.shiftKey,meta:e.metaKey}}function X(e){return function(e){W={click:R,dblclick:R,mousedown:R,mouseup:R,focus:null,blur:null,input:e.logDetails?function(e){return{value:e.target.value}}:null,change:e.logDetails?function(e){return{value:e.target.value}}:null,dragstart:null,dragend:null,drag:null,drop:null,keydown:e.logDetails?function(e){return{key:e.keyCode,ctrl:e.ctrlKey,alt:e.altKey,shift:e.shiftKey,meta:e.metaKey}}:null,mouseover:null},T={},I={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}}},K={submit:null}}(e),Object.keys(W).forEach((function(e){document.addEventListener(e,(function(t){D(t,W[e])}),!0)})),V.forEach((function(e){document.addEventListener(e,(function(e){!function(e){var t=B(e.target),n=P(e),o=e.type,r=Math.floor(e.timeStamp&&e.timeStamp>0?v.time(e.timeStamp):Date.now());if(null==y&&(y=t,b=o,S=n,O=r,k=0),y!==t||b!==o){E={target:y,path:S,pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:j(),count:k,duration:r-O,startTime:O,endTime:r,type:b,logType:"interval",targetChange:y!==t,typeChange:b!==o,userAction:!1,userId:v.userId,toolVersion:v.version,toolName:v.toolName,useraleVersion:v.useraleVersion,sessionID:v.sessionID};for(var i=0,a=Object.values(x);i<a.length;i++){var s=a[i];if("function"==typeof s&&!(E=s(E,null)))return!1}g.push(E),y=t,b=o,S=n,O=r,k=0}y==t&&b==o&&(k+=1)}(e)}),!0)})),Object.keys(I).forEach((function(t){T[t]=!0,window.addEventListener(t,(function(n){T[t]&&(T[t]=!1,D(n,I[t]),setTimeout((function(){T[t]=!0}),e.resolution))}),!0)})),Object.keys(K).forEach((function(e){document.addEventListener(e,(function(t){D(t,W[e])}),!0)})),L.forEach((function(e){window.addEventListener(e,(function(e){D(e,(function(){return{window:!0}}))}),!0)})),!0}var $=null;var _=null;function H(e,t){null!==_&&clearInterval(_),_=function(e,t){return setInterval((function(){t.on&&e.length>=t.logCountThreshold&&(z(e.slice(0),t,0),e.splice(0))}),t.transmitInterval)}(e,t),function(e,t){window.addEventListener("pagehide",(function(){t.on&&e.length>0&&(navigator.sendBeacon(t.url,JSON.stringify(e)),e.splice(0))}))}(e,t)}function z(e,t,n){var o=new XMLHttpRequest,r=JSON.stringify(e);o.open("POST",t.url),function(e){if($)try{e.authHeader=$()}catch(e){console.error("Error encountered while setting the auth header: ".concat(e))}}(t),t.authHeader&&o.setRequestHeader("Authorization",t.authHeader),o.setRequestHeader("Content-type","application/json;charset=UTF-8"),o.onreadystatechange=function(){4===o.readyState&&200!==o.status&&n>0&&z(e,t,n--)},o.send(r)}var F,U={},Y=[],J=Date.now();window.onload=function(){F=Date.now()},e.started=!1,U.on=!1,U.useraleVersion=n,r(U,function(){var e={};null===o&&(o=function(e,t){if(null===window.sessionStorage.getItem(e))return window.sessionStorage.setItem(e,JSON.stringify(t)),t;return JSON.parse(window.sessionStorage.getItem(e))}("userAleSessionId","session_"+String(Date.now())));var t,n=document.currentScript||(t=document.getElementsByTagName("script"))[t.length-1],r=n?n.getAttribute.bind(n):function(){return null};return e.autostart="false"!==r("data-autostart"),e.url=r("data-url")||"http://localhost:8000",e.transmitInterval=+r("data-interval")||5e3,e.logCountThreshold=+r("data-threshold")||5,e.userId=r("data-user")||null,e.version=r("data-version")||null,e.logDetails="true"===r("data-log-details"),e.resolution=+r("data-resolution")||500,e.toolName=r("data-tool")||null,e.userFromParams=r("data-user-from-params")||null,e.time=function(e){var t;if(e.timeStamp&&e.timeStamp>0){var n=Date.now()-e.timeStamp;if(n<0)t=function(){return e.timeStamp/1e3};else if(n>e.timeStamp){var o=performance.timing.navigationStart;t=function(e){return e+o}}else t=function(e){return e}}else t=function(){return Date.now()};return t}(document.createEvent("CustomEvent")),e.sessionID=r("data-session")||o,e.authHeader=r("data-auth")||null,e.custIndex=r("data-index")||null,e}()),g=Y,v=U,x=[],y=null,b=null,S=null,O=null,k=0,E=null,U.autostart&&function t(n){e.started||setTimeout((function(){var o=document.readyState;!n.autostart||"interactive"!==o&&"complete"!==o?t(n):(X(n),H(Y,n),e.started=n.on=!0,C({type:"load",logType:"raw",details:{pageLoadTime:F-J}},(function(){}),!1))}),100)}(U);var q=n;e.addCallbacks=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return t.forEach((function(e){var t=Object.keys(e).reduce((function(t,n){return t[n]=Object.getOwnPropertyDescriptor(e,n),t}),{});Object.getOwnPropertySymbols(e).forEach((function(n){var o=Object.getOwnPropertyDescriptor(e,n);o.enumerable&&(t[n]=o)})),Object.defineProperties(x,t)})),x},e.buildPath=P,e.details=function(e,t){return{click:R,dblclick:R,mousedown:R,mouseup:R,focus:null,blur:null,input:e.logDetails?function(e){return{value:e.target.value}}:null,change:e.logDetails?function(e){return{value:e.target.value}}:null,dragstart:null,dragend:null,drag:null,drop:null,keydown:e.logDetails?function(e){return{key:e.keyCode,ctrl:e.ctrlKey,alt:e.altKey,shift:e.shiftKey,meta:e.metaKey}}:null,mouseover:null,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}},submit:null}[t]},e.getSelector=B,e.log=function(e){return null!==e&&"object"===t(e)&&(Y.push(e),!0)},e.options=function(e){return void 0!==e&&r(U,e),U},e.packageCustomLog=C,e.packageLog=D,e.registerAuthCallback=function(e){try{return function(e){if("function"!=typeof e)throw new Error("Userale auth callback must be a function");var t=e();if("string"!=typeof t)throw new Error("Userale auth callback must return a string")}(e),$=e,!0}catch(e){return!1}},e.removeCallbacks=function(e){e.forEach((function(e){Object.hasOwn(x,e)&&delete x[e]}))},e.start=function(){e.started&&!1!==U.autostart||(e.started=U.on=!0,U.autostart=!0)},e.stop=function(){e.started=U.on=!1,U.autostart=!1},e.version=q}));
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).userale={})}(this,(function(e){"use strict";function t(e){return t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t(e)}var n="2.4.0",r=null;function o(e,t){var n=e.autostart,r=t.autostart;Object.keys(t).forEach((function(n){if("userFromParams"===n){var r=(o=t[n],i=new RegExp("[?&]"+o+"(=([^&#]*)|&|#|$)"),(a=window.location.href.match(i))&&a[2]?decodeURIComponent(a[2].replace(/\+/g," ")):null);r&&(e.userId=r)}var o,i,a;e[n]=t[n]})),!1!==n&&!1!==r||(e.autostart=!1)}var i=function(e,t,n){if(n||2===arguments.length)for(var r,o=0,i=t.length;o<i;o++)!r&&o in t||(r||(r=Array.prototype.slice.call(t,0,o)),r[o]=t[o]);return e.concat(r||Array.prototype.slice.call(t))},a=function(e,t,n){this.name=e,this.version=t,this.os=n,this.type="browser"},l=function(e){this.version=e,this.type="node",this.name="node",this.os=process.platform},u=function(e,t,n,r){this.name=e,this.version=t,this.os=n,this.bot=r,this.type="bot-device"},s=function(){this.type="bot",this.bot=!0,this.name="bot",this.version=null,this.os=null},c=function(){this.type="react-native",this.name="react-native",this.version=null,this.os=null},d=/(nuhk|curl|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask\ Jeeves\/Teoma|ia_archiver)/,f=3,m=[["aol",/AOLShield\/([0-9\._]+)/],["edge",/Edge\/([0-9\._]+)/],["edge-ios",/EdgiOS\/([0-9\._]+)/],["yandexbrowser",/YaBrowser\/([0-9\._]+)/],["kakaotalk",/KAKAOTALK\s([0-9\.]+)/],["samsung",/SamsungBrowser\/([0-9\.]+)/],["silk",/\bSilk\/([0-9._-]+)\b/],["miui",/MiuiBrowser\/([0-9\.]+)$/],["beaker",/BeakerBrowser\/([0-9\.]+)/],["edge-chromium",/EdgA?\/([0-9\.]+)/],["chromium-webview",/(?!Chrom.*OPR)wv\).*Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/],["chrome",/(?!Chrom.*OPR)Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/],["phantomjs",/PhantomJS\/([0-9\.]+)(:?\s|$)/],["crios",/CriOS\/([0-9\.]+)(:?\s|$)/],["firefox",/Firefox\/([0-9\.]+)(?:\s|$)/],["fxios",/FxiOS\/([0-9\.]+)/],["opera-mini",/Opera Mini.*Version\/([0-9\.]+)/],["opera",/Opera\/([0-9\.]+)(?:\s|$)/],["opera",/OPR\/([0-9\.]+)(:?\s|$)/],["pie",/^Microsoft Pocket Internet Explorer\/(\d+\.\d+)$/],["pie",/^Mozilla\/\d\.\d+\s\(compatible;\s(?:MSP?IE|MSInternet Explorer) (\d+\.\d+);.*Windows CE.*\)$/],["netfront",/^Mozilla\/\d\.\d+.*NetFront\/(\d.\d)/],["ie",/Trident\/7\.0.*rv\:([0-9\.]+).*\).*Gecko$/],["ie",/MSIE\s([0-9\.]+);.*Trident\/[4-7].0/],["ie",/MSIE\s(7\.0)/],["bb10",/BB10;\sTouch.*Version\/([0-9\.]+)/],["android",/Android\s([0-9\.]+)/],["ios",/Version\/([0-9\._]+).*Mobile.*Safari.*/],["safari",/Version\/([0-9\._]+).*Safari/],["facebook",/FB[AS]V\/([0-9\.]+)/],["instagram",/Instagram\s([0-9\.]+)/],["ios-webview",/AppleWebKit\/([0-9\.]+).*Mobile/],["ios-webview",/AppleWebKit\/([0-9\.]+).*Gecko\)$/],["curl",/^curl\/([0-9\.]+)$/],["searchbot",/alexa|bot|crawl(er|ing)|facebookexternalhit|feedburner|google web preview|nagios|postrank|pingdom|slurp|spider|yahoo!|yandex/]],h=[["iOS",/iP(hone|od|ad)/],["Android OS",/Android/],["BlackBerry OS",/BlackBerry|BB10/],["Windows Mobile",/IEMobile/],["Amazon OS",/Kindle/],["Windows 3.11",/Win16/],["Windows 95",/(Windows 95)|(Win95)|(Windows_95)/],["Windows 98",/(Windows 98)|(Win98)/],["Windows 2000",/(Windows NT 5.0)|(Windows 2000)/],["Windows XP",/(Windows NT 5.1)|(Windows XP)/],["Windows Server 2003",/(Windows NT 5.2)/],["Windows Vista",/(Windows NT 6.0)/],["Windows 7",/(Windows NT 6.1)/],["Windows 8",/(Windows NT 6.2)/],["Windows 8.1",/(Windows NT 6.3)/],["Windows 10",/(Windows NT 10.0)/],["Windows ME",/Windows ME/],["Windows CE",/Windows CE|WinCE|Microsoft Pocket Internet Explorer/],["Open BSD",/OpenBSD/],["Sun OS",/SunOS/],["Chrome OS",/CrOS/],["Linux",/(Linux)|(X11)/],["Mac OS",/(Mac_PowerPC)|(Macintosh)/],["QNX",/QNX/],["BeOS",/BeOS/],["OS/2",/OS\/2/]];function p(e){var t=function(e){return""!==e&&m.reduce((function(t,n){var r=n[0],o=n[1];if(t)return t;var i=o.exec(e);return!!i&&[r,i]}),!1)}(e);if(!t)return null;var n=t[0],r=t[1];if("searchbot"===n)return new s;var o=r[1]&&r[1].split(".").join("_").split("_").slice(0,3);o?o.length<f&&(o=i(i([],o,!0),function(e){for(var t=[],n=0;n<e;n++)t.push("0");return t}(f-o.length),!0)):o=[];var l=o.join("."),c=function(e){for(var t=0,n=h.length;t<n;t++){var r=h[t],o=r[0];if(r[1].exec(e))return o}return null}(e),p=d.exec(e);return p&&p[1]?new u(n,l,c,p[1]):new a(n,l,c)}var w,g,v,y,b,S,O,k,E,W,I,T,N=w?p(w):"undefined"==typeof document&&"undefined"!=typeof navigator&&"ReactNative"===navigator.product?new c:"undefined"!=typeof navigator?p(navigator.userAgent):"undefined"!=typeof process&&process.version?new l(process.version.slice(1)):null,x={};function A(e,t){if(!v.on)return!1;var n=null;t&&(n=t(e));for(var r,o=(r=e.timeStamp&&e.timeStamp>0?v.time(e.timeStamp):Date.now(),{milli:Math.floor(r),micro:Number((r%1).toFixed(3))}),i={target:M(e.target),path:B(e),pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),clientTime:o.milli,microTime:o.micro,location:C(e),scrnRes:D(),type:e.type,logType:"raw",userAction:!0,details:n,userId:v.userId,toolVersion:v.version,toolName:v.toolName,useraleVersion:v.useraleVersion,sessionID:v.sessionID},a=0,l=Object.values(x);a<l.length;a++){var u=l[a];if("function"==typeof u&&!(i=u(i,e)))return!1}return g.push(i),!0}function j(e,t,n){if(!v.on)return!1;var r=null;t&&(r=t());for(var o={pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),clientTime:Date.now(),scrnRes:D(),logType:"custom",userAction:n,details:r,userId:v.userId,toolVersion:v.version,toolName:v.toolName,useraleVersion:v.useraleVersion,sessionID:v.sessionID},i=Object.assign(o,e),a=0,l=Object.values(x);a<l.length;a++){var u=l[a];if("function"==typeof u&&!(i=u(i,null)))return!1}return g.push(i),!0}function C(e){return null!=e.pageX?{x:e.pageX,y:e.pageY}:null!=e.clientX?{x:document.documentElement.scrollLeft+e.clientX,y:document.documentElement.scrollTop+e.clientY}:{x:null,y:null}}function D(){return{width:window.innerWidth,height:window.innerHeight}}function M(e){return e.localName?e.localName+(e.id?"#"+e.id:"")+(e.className?"."+e.className:""):e.nodeName?e.nodeName+(e.id?"#"+e.id:"")+(e.className?"."+e.className:""):e&&e.document&&e.location&&e.alert&&e.setInterval?"Window":"Unknown"}function B(e){if(e instanceof window.Event)return function(e){var t,n=0,r=[];for(;t=e[n];)r.push(M(t)),++n;return r}(e.composedPath())}function P(){return{browser:N?N.name:"",version:N?N.version:""}}var K,V=["click","focus","blur","input","change","mouseover","submit"],L=["load","blur","focus"];function R(e){return{clicks:e.detail,ctrl:e.ctrlKey,alt:e.altKey,shift:e.shiftKey,meta:e.metaKey}}function $(e){return function(e){W={click:R,dblclick:R,mousedown:R,mouseup:R,focus:null,blur:null,input:e.logDetails?function(e){return{value:e.target.value}}:null,change:e.logDetails?function(e){return{value:e.target.value}}:null,dragstart:null,dragend:null,drag:null,drop:null,keydown:e.logDetails?function(e){return{key:e.keyCode,ctrl:e.ctrlKey,alt:e.altKey,shift:e.shiftKey,meta:e.metaKey}}:null,mouseover:null},I={},T={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}}},K={submit:null}}(e),Object.keys(W).forEach((function(e){document.addEventListener(e,(function(t){A(t,W[e])}),!0)})),V.forEach((function(e){document.addEventListener(e,(function(e){!function(e){var t=M(e.target),n=B(e),r=e.type,o=Math.floor(e.timeStamp&&e.timeStamp>0?v.time(e.timeStamp):Date.now());if(null==y&&(y=t,b=r,S=n,O=o,k=0),y!==t||b!==r){E={target:y,path:S,pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),count:k,duration:o-O,startTime:O,endTime:o,type:b,logType:"interval",targetChange:y!==t,typeChange:b!==r,userAction:!1,userId:v.userId,toolVersion:v.version,toolName:v.toolName,useraleVersion:v.useraleVersion,sessionID:v.sessionID};for(var i=0,a=Object.values(x);i<a.length;i++){var l=a[i];if("function"==typeof l&&!(E=l(E,null)))return!1}g.push(E),y=t,b=r,S=n,O=o,k=0}y==t&&b==r&&(k+=1)}(e)}),!0)})),Object.keys(T).forEach((function(t){I[t]=!0,window.addEventListener(t,(function(n){I[t]&&(I[t]=!1,A(n,T[t]),setTimeout((function(){I[t]=!0}),e.resolution))}),!0)})),Object.keys(K).forEach((function(e){document.addEventListener(e,(function(t){A(t,W[e])}),!0)})),L.forEach((function(e){window.addEventListener(e,(function(e){A(e,(function(){return{window:!0}}))}),!0)})),!0}function X(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}function _(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var r,o,i,a,l=[],u=!0,s=!1;try{if(i=(n=n.call(e)).next,0===t){if(Object(n)!==n)return;u=!1}else for(;!(u=(r=i.call(n)).done)&&(l.push(r.value),l.length!==t);u=!0);}catch(e){s=!0,o=e}finally{try{if(!u&&null!=n.return&&(a=n.return(),Object(a)!==a))return}finally{if(s)throw o}}return l}}(e,t)||function(e,t){if(e){if("string"==typeof e)return X(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?X(e,t):void 0}}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}var H=null;var U=null;function z(e,t){null!==U&&clearInterval(U),U=function(e,t){return setInterval((function(){t.on&&e.length>=t.logCountThreshold&&(F(e.slice(0),t,0),e.splice(0))}),t.transmitInterval)}(e,t),function(e,t){window.addEventListener("pagehide",(function(){t.on&&e.length>0&&(navigator.sendBeacon(t.url,JSON.stringify(e)),e.splice(0))}))}(e,t)}function F(e,t,n){var r=new XMLHttpRequest,o=JSON.stringify(e);r.open("POST",t.url),function(e){if(H)try{e.authHeader=H()}catch(e){console.error("Error encountered while setting the auth header: ".concat(e))}}(t),t.authHeader&&r.setRequestHeader("Authorization",t.authHeader),r.setRequestHeader("Content-type","application/json;charset=UTF-8"),t.headers&&Object.entries(t.headers).forEach((function(e){var t=_(e,2),n=t[0],o=t[1];r.setRequestHeader(n,o)})),r.onreadystatechange=function(){4===r.readyState&&200!==r.status&&n>0&&F(e,t,n--)},r.send(o)}var Y,J={},q=[],G=Date.now();window.onload=function(){Y=Date.now()},e.started=!1,J.on=!1,J.useraleVersion=n,o(J,function(){var e={};null===r&&(r=function(e,t){if(null===window.sessionStorage.getItem(e))return window.sessionStorage.setItem(e,JSON.stringify(t)),t;return JSON.parse(window.sessionStorage.getItem(e))}("userAleSessionId","session_"+String(Date.now())));var t,n=document.currentScript||(t=document.getElementsByTagName("script"))[t.length-1],o=n?n.getAttribute.bind(n):function(){return null};return e.autostart="false"!==o("data-autostart"),e.url=o("data-url")||"http://localhost:8000",e.transmitInterval=+o("data-interval")||5e3,e.logCountThreshold=+o("data-threshold")||5,e.userId=o("data-user")||null,e.version=o("data-version")||null,e.logDetails="true"===o("data-log-details"),e.resolution=+o("data-resolution")||500,e.toolName=o("data-tool")||null,e.userFromParams=o("data-user-from-params")||null,e.time=function(e){var t;if(e.timeStamp&&e.timeStamp>0){var n=Date.now()-e.timeStamp;if(n<0)t=function(){return e.timeStamp/1e3};else if(n>e.timeStamp){var r=performance.timing.navigationStart;t=function(e){return e+r}}else t=function(e){return e}}else t=function(){return Date.now()};return t}(document.createEvent("CustomEvent")),e.sessionID=o("data-session")||r,e.authHeader=o("data-auth")||null,e.custIndex=o("data-index")||null,e.headers=o("data-headers")||null,e}()),g=q,v=J,x=[],y=null,b=null,S=null,O=null,k=0,E=null,J.autostart&&function t(n){e.started||setTimeout((function(){var r=document.readyState;!n.autostart||"interactive"!==r&&"complete"!==r?t(n):($(n),z(q,n),e.started=n.on=!0,j({type:"load",logType:"raw",details:{pageLoadTime:Y-G}},(function(){}),!1))}),100)}(J);var Q=n;e.addCallbacks=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return t.forEach((function(e){var t=Object.keys(e).reduce((function(t,n){return t[n]=Object.getOwnPropertyDescriptor(e,n),t}),{});Object.getOwnPropertySymbols(e).forEach((function(n){var r=Object.getOwnPropertyDescriptor(e,n);r.enumerable&&(t[n]=r)})),Object.defineProperties(x,t)})),x},e.buildPath=B,e.details=function(e,t){return{click:R,dblclick:R,mousedown:R,mouseup:R,focus:null,blur:null,input:e.logDetails?function(e){return{value:e.target.value}}:null,change:e.logDetails?function(e){return{value:e.target.value}}:null,dragstart:null,dragend:null,drag:null,drop:null,keydown:e.logDetails?function(e){return{key:e.keyCode,ctrl:e.ctrlKey,alt:e.altKey,shift:e.shiftKey,meta:e.metaKey}}:null,mouseover:null,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}},submit:null}[t]},e.getSelector=M,e.log=function(e){return null!==e&&"object"===t(e)&&(q.push(e),!0)},e.options=function(e){return void 0!==e&&o(J,e),J},e.packageCustomLog=j,e.packageLog=A,e.registerAuthCallback=function(e){try{return function(e){if("function"!=typeof e)throw new Error("Userale auth callback must be a function");var t=e();if("string"!=typeof t)throw new Error("Userale auth callback must return a string")}(e),H=e,!0}catch(e){return!1}},e.removeCallbacks=function(e){e.forEach((function(e){Object.hasOwn(x,e)&&delete x[e]}))},e.start=function(){e.started&&!1!==J.autostart||(e.started=J.on=!0,J.autostart=!0)},e.stop=function(){e.started=J.on=!1,J.autostart=!1},e.version=Q}));
diff --git a/package-lock.json b/package-lock.json
index f96f0fe..f728a5e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,6 +21,7 @@
         "@rollup/plugin-terser": "^0.4.4",
         "body-parser": "^1.20.2",
         "chai": "^4.3.10",
+        "chai-subset": "^1.6.0",
         "cypress": "^13.6.0",
         "detect-browser": "^5.3.0",
         "dom-storage": "^2.1.0",
@@ -3144,6 +3145,15 @@
         "node": ">=4"
       }
     },
+    "node_modules/chai-subset": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/chai-subset/-/chai-subset-1.6.0.tgz",
+      "integrity": "sha512-K3d+KmqdS5XKW5DWPd5sgNffL3uxdDe+6GdnJh3AYPhwnBGRY5urfvfcbRtWIvvpz+KxkL9FeBB6MZewLUNwug==",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/chalk": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
diff --git a/package.json b/package.json
index 04a6ff4..7da9de1 100644
--- a/package.json
+++ b/package.json
@@ -61,6 +61,7 @@
     "@rollup/plugin-terser": "^0.4.4",
     "body-parser": "^1.20.2",
     "chai": "^4.3.10",
+    "chai-subset": "^1.6.0",
     "cypress": "^13.6.0",
     "detect-browser": "^5.3.0",
     "dom-storage": "^2.1.0",
diff --git a/src/getInitialSettings.js b/src/getInitialSettings.js
index 174c642..b9624eb 100644
--- a/src/getInitialSettings.js
+++ b/src/getInitialSettings.js
@@ -51,6 +51,7 @@
     settings.sessionID = get('data-session') || sessionId;
     settings.authHeader = get('data-auth') || null;
     settings.custIndex = get('data-index') || null;
+    settings.headers = get('data-headers') || null;
     return settings;
 }
 
diff --git a/src/main.js b/src/main.js
index f34c080..01ba09b 100644
--- a/src/main.js
+++ b/src/main.js
@@ -32,7 +32,7 @@
 
 export let started = false;
 export {defineCustomDetails as details} from './attachHandlers.js';
-export {registerAuthCallback as registerAuthCallback} from './auth.js';
+export {registerAuthCallback as registerAuthCallback} from './utils';
 export {
     addCallbacks as addCallbacks,
     removeCallbacks as removeCallbacks,
diff --git a/src/sendLogs.js b/src/sendLogs.js
index d420fa0..5985644 100644
--- a/src/sendLogs.js
+++ b/src/sendLogs.js
@@ -15,7 +15,8 @@
  * limitations under the License.
  */
 
-import { updateAuthHeader } from "./auth.js";
+import { updateAuthHeader } from "./utils";
+import { updateCustomHeaders } from "./utils/headers";
 
 let sendIntervalId = null;
 
@@ -94,6 +95,15 @@
   }
   req.setRequestHeader("Content-type", "application/json;charset=UTF-8");
 
+  // Update custom headers last to allow them to over-write the defaults. This assumes
+  // the user knows what they are doing and may want to over-write the defaults.
+  updateCustomHeaders(config);
+  if (config.headers) {
+    Object.entries(config.headers).forEach(([header, value]) => {
+      req.setRequestHeader(header, value);
+    });
+  }
+
   req.onreadystatechange = function () {
     if (req.readyState === 4 && req.status !== 200) {
       if (retries > 0) {
diff --git a/src/auth.js b/src/utils/auth/index.js
similarity index 100%
rename from src/auth.js
rename to src/utils/auth/index.js
diff --git a/src/utils/headers/index.js b/src/utils/headers/index.js
new file mode 100644
index 0000000..e0c599f
--- /dev/null
+++ b/src/utils/headers/index.js
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+export let headersCallback = null;
+
+/**
+ * Fetches the most up-to-date custom headers object from the headers callback
+ * and updates the config object with the new value.
+ * @param {Object} config Configuration object to be updated.
+ * @param {Function} headersCallback Callback used to fetch the newest headers.
+ * @returns {void}
+ */
+export function updateCustomHeaders(config) {
+  if (headersCallback) {
+    try {
+        config.headers = headersCallback();
+    } catch (e) {
+        // We should emit the error, but otherwise continue as this could be a temporary issue
+        // due to network connectivity or some logic inside the headersCallback which is the user's
+        // responsibility.
+        console.error(`Error encountered while setting the headers: ${e}`);
+    }
+  }
+}
+
+/**
+ * Registers the provided callback to be used when updating the auth header.
+ * @param {Function} callback Callback used to fetch the newest headers. Should return an object.
+ * @returns {boolean} Whether the operation succeeded.
+ */
+export function registerHeadersCallback(callback) {
+  try {
+    verifyCallback(callback);
+    headersCallback = callback;
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
+/**
+ * Verify that the provided callback is a function which returns a string
+ * @param {Function} callback Callback used to fetch the newest header. Should return an object.
+ * @throws {Error} If the callback is not a function or does not return a string.
+ * @returns {void}
+ */
+export function verifyCallback(callback) {
+  if (typeof callback !== "function") {
+    throw new Error("Userale headers callback must be a function");
+  }
+  const result = callback();
+  if (typeof result !== "object") {
+    throw new Error("Userale headers callback must return an object");
+  }
+  for (const [key, value] of Object.entries(result)) {
+    if (typeof key !== "string" || typeof value !== "string") {
+      throw new Error("Userale header callback must return an object with string keys and values");
+    }
+  }
+}
+
+/**
+ * Resets the authCallback to null. Used for primarily for testing, but could be used
+ * to remove the callback in production.
+ * @returns {void}
+ */
+export function resetHeadersCallback() {
+  headersCallback = null;
+}
\ No newline at end of file
diff --git a/src/utils/index.js b/src/utils/index.js
new file mode 100644
index 0000000..243cbbe
--- /dev/null
+++ b/src/utils/index.js
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+export {
+    authCallback,
+    updateAuthHeader, 
+    registerAuthCallback, 
+    resetAuthCallback, 
+    verifyCallback as verifyAuthCallback
+} from "./auth";
+export { 
+    headersCallback,
+    updateCustomHeaders,
+    registerHeadersCallback,
+    resetHeadersCallback,
+    verifyCallback as verifyHeadersCallback
+} from "./headers";
\ No newline at end of file
diff --git a/test/auth_spec.js b/test/auth_spec.js
index 86029cb..db9b7c7 100644
--- a/test/auth_spec.js
+++ b/test/auth_spec.js
@@ -16,27 +16,33 @@
  */
 import {expect} from 'chai';
 import sinon from 'sinon';
-import {authCallback, registerAuthCallback, resetAuthCallback, updateAuthHeader, verifyCallback} from '../src/auth';
+import {
+    authCallback,
+    registerAuthCallback,
+    resetAuthCallback,
+    updateAuthHeader,
+    verifyAuthCallback
+} from '../src/utils';
 
 describe('verifyCallback', () => {
     it('should not throw error for valid callback', () => {
         const validCallback = sinon.stub().returns('someString');
-        expect(() => verifyCallback(validCallback)).to.not.throw();
+        expect(() => verifyAuthCallback(validCallback)).to.not.throw();
     });
 
     it('should throw error for non-function callback', () => {
         const nonFunctionCallback = 'notAFunction';
-        expect(() => verifyCallback(nonFunctionCallback)).to.throw('Userale auth callback must be a function');
+        expect(() => verifyAuthCallback(nonFunctionCallback)).to.throw('Userale auth callback must be a function');
     });
 
     it('should throw error for non-string callback return', () => {
         const invalidReturnCallback = sinon.stub().returns(123);
-        expect(() => verifyCallback(invalidReturnCallback)).to.throw('Userale auth callback must return a string');
+        expect(() => verifyAuthCallback(invalidReturnCallback)).to.throw('Userale auth callback must return a string');
     });
 
     it('should not throw error for valid callback with empty string return', () => {
         const validCallback = sinon.stub().returns('');
-        expect(() => verifyCallback(validCallback)).to.not.throw();
+        expect(() => verifyAuthCallback(validCallback)).to.not.throw();
     });
 });
 
diff --git a/test/headers_spec.js b/test/headers_spec.js
new file mode 100644
index 0000000..a8bba1d
--- /dev/null
+++ b/test/headers_spec.js
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+import {expect} from 'chai';
+import sinon from 'sinon';
+import {
+    headersCallback,
+    registerHeadersCallback,
+    resetHeadersCallback,
+    updateCustomHeaders,
+    verifyHeadersCallback
+} from '../src/utils';
+
+describe('verifyCallback', () => {
+    it('should not throw error for valid callback', () => {
+        const validCallback = sinon.stub().returns({'x-api-token': 'someString', 'x-abc-def': 'someOtherString'});
+        expect(() => verifyHeadersCallback(validCallback)).to.not.throw();
+    });
+
+    it('should throw error for non-function callback', () => {
+        const nonFunctionCallback = 'notAFunction';
+        expect(() => verifyHeadersCallback(nonFunctionCallback)).to.throw('Userale headers callback must be a function');
+    });
+
+    it('should throw error for non-object callback return', () => {
+        const invalidReturnCallback = sinon.stub().returns(123);
+        expect(() => verifyHeadersCallback(invalidReturnCallback)).to.throw('Userale headers callback must return an object');
+    });
+
+    it('should throw error for incorrect headers object return', () => {
+        const invalidReturnCallback = sinon.stub().returns({'x-not-a-proper-value': 123});
+        expect(() => verifyHeadersCallback(invalidReturnCallback)).to.throw('Userale header callback must return an object with string keys and values');
+    });
+
+    it('should not throw error for valid callback with empty object return', () => {
+        const validCallback = sinon.stub().returns({});
+        expect(() => verifyHeadersCallback(validCallback)).to.not.throw();
+    });
+});
+
+describe('registerHeadersCallback', () => {
+    afterEach(() => {
+      resetHeadersCallback();
+    });
+  
+    it('should register a valid callback', () => {
+      const validCallback = sinon.stub().returns({'x-api-token': 'someString', 'x-abc-def': 'someOtherString'});
+      expect(registerHeadersCallback(validCallback)).to.be.true;
+      expect(headersCallback).to.equal(validCallback);
+    });
+  
+    it('should not register a non-function callback', () => {
+      const nonFunctionCallback = 'notAFunction';
+      expect(registerHeadersCallback(nonFunctionCallback)).to.be.false;
+      expect(headersCallback).to.be.null;
+    });
+  
+    it('should not register a callback with invalid return type', () => {
+      const invalidReturnCallback = sinon.stub().returns(123);
+      expect(registerHeadersCallback(invalidReturnCallback)).to.be.false;
+      expect(headersCallback).to.be.null;
+    });
+  
+    it('should register a callback with empty object return', () => {
+      const validCallback = sinon.stub().returns({});
+      expect(registerHeadersCallback(validCallback)).to.be.true;
+      expect(headersCallback).to.equal(validCallback);
+    });
+});
+
+describe('updateCustomHeader', () => {
+    let config;
+  
+    beforeEach(() => {
+      // Initialize config object before each test
+      config = { headers: null };
+    });
+
+    afterEach(() => {
+      resetHeadersCallback();
+    });
+  
+    it('should update custom headers when headersCallback is provided', () => {
+      const customHeaders = {'x-api-token': 'someString', 'x-abc-def': 'someOtherString'}
+      const validCallback = sinon.stub().returns(customHeaders);
+      registerHeadersCallback(validCallback);
+      updateCustomHeaders(config, headersCallback);
+      expect(config.headers).to.equal(customHeaders);
+    });
+  
+    it('should not update custom headers when headersCallback is not provided', () => {
+      updateCustomHeaders(config, headersCallback);
+      expect(config.headers).to.be.null;
+    });
+  
+    it('should not update custom headers when headersCallback returns non-object', () => {
+      const invalidReturnCallback = sinon.stub().returns(123);
+      registerHeadersCallback(invalidReturnCallback);
+      updateCustomHeaders(config, headersCallback);
+      expect(config.headers).to.be.null;
+    });
+  
+    it('should update custom headers with empty string return from headersCallback', () => {
+      const validCallback = sinon.stub().returns({});
+      registerHeadersCallback(validCallback);
+      updateCustomHeaders(config, headersCallback);
+      expect(config.headers).to.deep.equal({});
+    });
+  
+    it('should handle errors thrown during headersCallback execution', () => {
+      const errorThrowingCallback = sinon.stub().throws(new Error('Callback execution failed'));
+      registerHeadersCallback(errorThrowingCallback);
+      updateCustomHeaders(config, headersCallback);
+      expect(config.headers).to.be.null;
+    });
+  
+    it('should not update custom headers after unregistering headersCallback', () => {
+      const customHeaders = {'x-api-token': 'someString', 'x-abc-def': 'someOtherString'}
+      const validCallback = sinon.stub().returns(customHeaders);
+      registerHeadersCallback(validCallback);
+      updateCustomHeaders(config, headersCallback);
+      expect(config.headers).to.equal(customHeaders);
+      
+      // Unregister headersCallback
+      updateCustomHeaders(config, null);
+      expect(config.headers).to.equal(customHeaders);
+    });
+  });
\ No newline at end of file
diff --git a/test/main_spec.js b/test/main_spec.js
index 8f66d1e..5bc045a 100644
--- a/test/main_spec.js
+++ b/test/main_spec.js
@@ -39,6 +39,7 @@
             'userFromParams',
             'time',
             'authHeader',
+            'headers',
             'custIndex'
         ]);
         dom.window.close();
diff --git a/test/sendLogs_spec.js b/test/sendLogs_spec.js
index 6059724..7b50802 100644
--- a/test/sendLogs_spec.js
+++ b/test/sendLogs_spec.js
@@ -14,13 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {expect} from 'chai';
+import chai, {expect} from 'chai';
+import chaiSubset from 'chai-subset';
 import {JSDOM} from 'jsdom';
 import sinon from 'sinon';
 import {initSender, sendOnInterval, sendOnClose} from '../src/sendLogs';
-import {registerAuthCallback} from '../src/auth';
+import {registerAuthCallback, registerHeadersCallback} from '../src/utils';
 import 'global-jsdom/register'
 
+chai.use(chaiSubset);
+
 describe('sendLogs', () => {
     it('sends logs on an interval', (done) => {
         let requests = 0;
@@ -112,7 +115,7 @@
         sinon.assert.notCalled(sendBeaconSpy)
     });
 
-    it('sends logs with proper auth header when using registerCallback', (done) => {
+    it('sends logs with proper auth header when using registerAuthCallback', (done) => {
         let requests = []
         const originalXMLHttpRequest = global.XMLHttpRequest;
         const conf = { on: true, transmitInterval: 500, url: 'test', logCountThreshold: 1 };
@@ -149,4 +152,43 @@
         global.XMLHttpRequest = originalXMLHttpRequest;
         done()
       });
+
+      it('sends logs with proper custom headers when using registerHeadersCallback', (done) => {
+        let requests = []
+        const originalXMLHttpRequest = global.XMLHttpRequest;
+        const conf = { on: true, transmitInterval: 500, url: 'test', logCountThreshold: 1 };
+        const logs = [];
+        const clock = sinon.useFakeTimers();
+        const xhr = sinon.useFakeXMLHttpRequest();
+        global.XMLHttpRequest = xhr;
+        xhr.onCreate = (xhr) => {
+            requests.push(xhr);
+        };
+    
+        // Mock the authCallback function
+        const customHeaders = {'x-api-token': 'someString', 'x-abc-def': 'someOtherString'}
+        const headersCallback = sinon.stub().returns(customHeaders);
+        
+        // Register the authCallback
+        registerHeadersCallback(headersCallback);
+    
+        // Initialize sender with logs and config
+        initSender(logs, conf);
+    
+        // Simulate log entry
+        logs.push({ foo: 'bar' });
+    
+        // Trigger interval to send logs
+        clock.tick(conf.transmitInterval);
+
+        // Verify that the request has the proper auth header
+        expect(requests.length).to.equal(1);
+        expect(requests[0].requestHeaders).to.containSubset(customHeaders);
+    
+        // Restore XMLHttpRequest and clock
+        xhr.restore();
+        clock.restore();
+        global.XMLHttpRequest = originalXMLHttpRequest;
+        done()
+      });
 });