Repository: snowplow/snowplow-javascript-tracker (ts)

Issue:
Handle errors in tracker callback
If errors occur in the tracker callback method e.g:
```
            window.snowplow(function() {
                var sp = this.sp;
                sp.thisWillError(); //Function doesn't exist
            });
```

This will prevent the tracker from continuing to process the event queue. Whilst this is an unlikely scenario, it can exhibit itself when multiple versions of the tracker are loaded on the same page. This results in a race condition (#560) where either version of the tracker can run the callback function (and may not have the desired function on the tracker). 

Ideally, we wouldn't see two incompatible versions of sp.js loaded on the same page but as this can happen (and has happened) then we should at least handle these errors so the tracker can continue to process events.

Pull request description:
Handle errors in tracker callback (close #784)

Patch:
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..93f13619
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,2 @@
+node_modules
+npm-debug.log
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..18ffea04
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,11 @@
+FROM node:8.12.0-alpine
+
+RUN apk add git
+RUN npm install -g grunt-cli 
+
+WORKDIR /usr/src/app
+
+COPY package.json ./
+RUN npm install
+
+COPY . .
diff --git a/README.md b/README.md
index 00d2f4f6..c1b25374 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,6 @@
 [![Build Status][travis-image]][travis]
 [![Selenium Test Status][saucelabs-button-image]][saucelabs]
 [![Code Climate][codeclimate-image]][codeclimate]
-[![Built with Grunt][grunt-image]][grunt]
 [![License][license-image]][bsd]
 [![jsDelivr Hits](https://data.jsdelivr.com/v1/package/gh/snowplow/snowplow-javascript-tracker/badge?style=rounded)](https://www.jsdelivr.com/package/gh/snowplow/snowplow-javascript-tracker)
 
@@ -27,16 +26,16 @@ client-side tier of your websites and web apps.
 
 ### Contributing quickstart
 
-Assuming git, [Vagrant][vagrant-install] and [VirtualBox][virtualbox-install] installed:
+Assuming git and [Docker][docker-install] installed:
 
 ```
  host$ git clone https://github.com/snowplow/snowplow-javascript-tracker.git
  host$ cd snowplow-javascript-tracker
- host$ vagrant up && vagrant ssh
-guest$ cd /vagrant
-guest$ sudo npm install
-guest$ cd core
-guest$ sudo npm install
+ host$ docker build -t tracker .
+ host$ docker run tracker grunt
+ host$ cd core
+ host$ docker build -t core .
+ host$ docker run core grunt
 ```
 
 Set up an `./aws.json` file using the example `./aws.sample.json`. If you just want to concat +
@@ -61,8 +60,7 @@ Licensed under the [Simplified BSD][bsd] license.
 
 [snowplow]: http://snowplowanalytics.com/
 
-[vagrant-install]: http://docs.vagrantup.com/v2/installation/index.html
-[virtualbox-install]: https://www.virtualbox.org/wiki/Downloads
+[docker-install]: https://docs.docker.com/install/
 
 [piwik]: http://piwik.org/
 [piwikjs]: https://github.com/piwik/piwik/blob/master/js/piwik.js
diff --git a/Vagrantfile b/Vagrantfile
deleted file mode 100644
index 8d19a0ef..00000000
--- a/Vagrantfile
+++ /dev/null
@@ -1,29 +0,0 @@
-Vagrant.configure("2") do |config|
-
-  config.vm.box = "ubuntu/trusty64"
-  config.vm.hostname = "snowplow-javascript-tracker"
-  config.ssh.forward_agent = true
-
-  config.vm.provider :virtualbox do |vb|
-    vb.name = Dir.pwd().split("/")[-1] + "-" + Time.now.to_f.to_i.to_s
-    vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
-    vb.customize [ "guestproperty", "set", :id, "--timesync-threshold", 10000 ]
-    # node is not particularly memory hungry
-    vb.memory = 1024
-  end
-
-  config.vm.provision :shell do |sh|
-    sh.path = "vagrant/up.bash"
-  end
-
-  # Requires Vagrant 1.7.0+
-  config.push.define "push-tracker", strategy: "local-exec" do |push|
-    push.script = "vagrant/push/publish-tracker.bash"
-  end
-
-  # Requires Vagrant 1.7.0+
-  config.push.define "push-core", strategy: "local-exec" do |push|
-    push.script = "vagrant/push/publish-core.bash"
-  end
-
-end
diff --git a/core/.dockerignore b/core/.dockerignore
new file mode 100644
index 00000000..93f13619
--- /dev/null
+++ b/core/.dockerignore
@@ -0,0 +1,2 @@
+node_modules
+npm-debug.log
diff --git a/core/Dockerfile b/core/Dockerfile
new file mode 100644
index 00000000..d173194b
--- /dev/null
+++ b/core/Dockerfile
@@ -0,0 +1,10 @@
+FROM node:8.12.0-alpine
+
+RUN npm install -g grunt-cli 
+
+WORKDIR /usr/src/app
+
+COPY package*.json ./
+RUN npm install
+
+COPY . .
diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json
index d1c49463..04f56f29 100644
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -5406,8 +5406,7 @@
         "ansi-regex": {
           "version": "2.1.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "aproba": {
           "version": "1.2.0",
@@ -5428,14 +5427,12 @@
         "balanced-match": {
           "version": "1.0.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -5450,20 +5447,17 @@
         "code-point-at": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "concat-map": {
           "version": "0.0.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "console-control-strings": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -5580,8 +5574,7 @@
         "inherits": {
           "version": "2.0.3",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "ini": {
           "version": "1.3.5",
@@ -5593,7 +5586,6 @@
           "version": "1.0.0",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -5608,7 +5600,6 @@
           "version": "3.0.4",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
@@ -5616,14 +5607,12 @@
         "minimist": {
           "version": "0.0.8",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "minipass": {
           "version": "2.3.5",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.2",
             "yallist": "^3.0.0"
@@ -5642,7 +5631,6 @@
           "version": "0.5.1",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -5723,8 +5711,7 @@
         "number-is-nan": {
           "version": "1.0.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "object-assign": {
           "version": "4.1.1",
@@ -5736,7 +5723,6 @@
           "version": "1.4.0",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -5822,8 +5808,7 @@
         "safe-buffer": {
           "version": "5.1.2",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "safer-buffer": {
           "version": "2.1.2",
@@ -5859,7 +5844,6 @@
           "version": "1.0.2",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -5879,7 +5863,6 @@
           "version": "3.0.1",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
@@ -5923,14 +5906,12 @@
         "wrappy": {
           "version": "1.0.2",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "yallist": {
           "version": "3.0.3",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         }
       }
     },
@@ -9276,11 +9257,6 @@
       "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
       "dev": true
     },
-    "murmurhash": {
-      "version": "0.0.2",
-      "resolved": "https://registry.npmjs.org/murmurhash/-/murmurhash-0.0.2.tgz",
-      "integrity": "sha1-bwe9ihEF5wnCb8iUIMtZMMJFhf4="
-    },
     "mute-stream": {
       "version": "0.0.5",
       "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
diff --git a/package.json b/package.json
index d385e68b..5d011509 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,6 @@
   "dependencies": {
     "browser-cookie-lite": "^1.0.4",
     "jstimezonedetect": "1.0.5",
-    "murmurhash": "0.0.2",
     "sha1": "git://github.com/pvorb/node-sha1.git#910081c83f3661507d9d89e66e3f38d8b59d5559",
     "snowplow-tracker-core": "^0.7.2",
     "uuid": "^3.3.3"
@@ -69,6 +68,8 @@
     "test:e2e:local": "docker pull snowplow/snowplow-micro && wdio tests/wdio.local.conf.js"
   },
   "jest": {
-    "setupFiles": ["jest-date-mock"]
+    "setupFiles": [
+      "jest-date-mock"
+    ]
   }
 }
diff --git a/src/js/in_queue.js b/src/js/in_queue.js
index f6ff7232..fa2ece2e 100644
--- a/src/js/in_queue.js
+++ b/src/js/in_queue.js
@@ -154,8 +154,15 @@
 
 				// Custom callback rather than tracker method, called with trackerDictionary as the context
 				if (isFunction(input)) {
-					input.apply(trackerDictionary, parameterArray);
-					continue;
+					try {
+						input.apply(trackerDictionary, parameterArray);
+					} 
+					catch(e) {
+						helpers.warn(`Custom callback error - ${e}`);
+					} 
+					finally {
+						continue;
+					}
 				}
 
 				parsedString = parseInputString(input);
diff --git a/src/js/lib/detectors.js b/src/js/lib/detectors.js
index e9c11b20..c63b296b 100644
--- a/src/js/lib/detectors.js
+++ b/src/js/lib/detectors.js
@@ -37,7 +37,6 @@
 	var
 		isFunction = require('lodash/isFunction'),
 		isUndefined = require('lodash/isUndefined'),
-		murmurhash3_32_gc = require('murmurhash').v3,
 		tz = require('jstimezonedetect').jstz.determine(),
 		cookie = require('browser-cookie-lite'),
 
@@ -107,39 +106,6 @@
 		return navigatorAlias.cookieEnabled ? '1' : '0';
 	};
 
-	/**
-	 * JS Implementation for browser fingerprint.
-	 * Does not require any external resources.
-	 * Based on https://github.com/carlo/jquery-browser-fingerprint
-	 * @return {number} 32-bit positive integer hash 
-	 */
-	object.detectSignature = function(hashSeed) {
-
-		var fingerprint = [
-			navigatorAlias.userAgent,
-			[ screenAlias.height, screenAlias.width, screenAlias.colorDepth ].join("x"),
-			( new Date() ).getTimezoneOffset(),
-			object.hasSessionStorage(),
-			object.hasLocalStorage()
-		];
-
-		var plugins = [];
-		if (navigatorAlias.plugins)
-		{
-			for(var i = 0; i < navigatorAlias.plugins.length; i++)
-			{
-				if (navigatorAlias.plugins[i]) {
-					var mt = [];
-					for(var j = 0; j < navigatorAlias.plugins[i].length; j++) {
-						mt.push([navigatorAlias.plugins[i][j].type, navigatorAlias.plugins[i][j].suffixes]);
-					}
-					plugins.push([navigatorAlias.plugins[i].name + "::" + navigatorAlias.plugins[i].description, mt.join("~")]);
-				}
-			}
-		}
-		return murmurhash3_32_gc(fingerprint.join("###") + "###" + plugins.sort().join(";"), hashSeed);
-	};
-
 	/*
 	 * Returns visitor timezone
 	 */
diff --git a/src/js/tracker.js b/src/js/tracker.js
index 27ecfc62..28d236b0 100755
--- a/src/js/tracker.js
+++ b/src/js/tracker.js
@@ -68,7 +68,6 @@
 	 * 4. appId, ''
 	 * 5. platform, 'web'
 	 * 6. respectDoNotTrack, false
-	 * 7. userFingerprintSeed, 123412414
 	 * 8. pageUnloadTimer, 500
 	 * 9. forceSecureTracker, false
 	 * 10. forceUnsecureTracker, false
@@ -226,9 +225,6 @@
 			// Life of the session cookie (in seconds)
 			configSessionCookieTimeout = argmap.hasOwnProperty('sessionCookieTimeout') ? argmap.sessionCookieTimeout : 1800, // 30 minutes
 
-			// Default hash seed for MurmurHash3 in detectors.detectSignature
-			configUserFingerprintHashSeed = argmap.hasOwnProperty('userFingerprintSeed') ? argmap.userFingerprintSeed : 123412414,
-
 			// Document character set
 			documentCharset = documentAlias.characterSet || documentAlias.charset,
 
@@ -267,9 +263,6 @@
 					configStateStorageStrategy == 'cookieAndLocalStorage',
 				getSnowplowCookieName('testcookie')),
 
-			// Visitor fingerprint
-			userFingerprint = (argmap.userFingerprint === false) ? '' : detectors.detectSignature(configUserFingerprintHashSeed),
-
 			// Unique ID for the tracker instance used to mark links which are being tracked
 			trackerId = functionName + '_' + namespace,
 
@@ -672,7 +665,7 @@
 		}
 
 		/**
-		 * Generate a pseudo-unique ID to fingerprint this user
+		 * Generate a pseudo-unique ID to identify this user
 		 */
 		function createNewDomainUserId() {
 			return uuid.v4();
@@ -783,7 +776,6 @@
 				toOptoutByCookie = false;
 			}
 
-
 			if ((configDoNotTrack || toOptoutByCookie) &&
 					configStateStorageStrategy != 'none') {
 				if (configStateStorageStrategy == 'localStorage') {
@@ -827,7 +819,6 @@
 			sb.add('vid', memorizedVisitCount);
 			sb.add('sid', memorizedSessionId);
 			sb.add('duid', _domainUserId); // Set to our local variable
-			sb.add('fp', userFingerprint);
 			sb.add('uid', businessUserId);
 
 			refreshUrl();
@@ -958,14 +949,6 @@
 				}
 			}
 
-			// Add Augur Context
-			if (autoContexts.augurIdentityLite) {
-				var augurIdentityLiteContext = getAugurIdentityLiteContext();
-				if (augurIdentityLiteContext) {
-					combinedContexts.push(augurIdentityLiteContext);
-				}
-			}
-
 			//Add Parrable Context
 			if (autoContexts.parrable) {
 				var parrableContext = getParrableContext();
@@ -1354,32 +1337,6 @@
 			});
 		}
 
-		/**
-		 * Creates a context from the window['augur'] object
-		 *
-		 * @return object The IdentityLite context
-		 */
-		function getAugurIdentityLiteContext() {
-			var augur = windowAlias.augur;
-			if (augur) {
-				var context = { consumer: {}, device: {} };
-				var consumer = augur.consumer || {};
-				context.consumer.UUID = consumer.UID;
-				var device = augur.device || {};
-				context.device.ID = device.ID;
-				context.device.isBot = device.isBot;
-				context.device.isProxied = device.isProxied;
-				context.device.isTor = device.isTor;
-				var fingerprint = device.fingerprint || {};
-				context.device.isIncognito = fingerprint.browserHasIncognitoEnabled;
-
-				return {
-					schema: 'iglu:io.augur.snowplow/identity_lite/jsonschema/1-0-0',
-					data: context
-				};
-			}
-		}
-
 		/**
 		 * Creates a context from the window['_hawk'] object
 		 *
@@ -1860,7 +1817,8 @@
 		 * @return string The user fingerprint
 		 */
 		apiMethods.getUserFingerprint = function () {
-			return userFingerprint;
+			helpers.warn('User Fingerprinting is no longer supported. This function will be removed in a future release.');
+			return 0;
 		};
 
 		/**
@@ -1974,21 +1932,15 @@
 		/**
 		 * @param number seed The seed used for MurmurHash3
 		 */
-		apiMethods.setUserFingerprintSeed = function(seed) {
-			helpers.warn('setUserFingerprintSeed is deprecated. Instead add a "userFingerprintSeed" field to the argmap argument of newTracker.');
-			configUserFingerprintHashSeed = seed;
-			userFingerprint = detectors.detectSignature(configUserFingerprintHashSeed);
+		apiMethods.setUserFingerprintSeed = function() {
+			helpers.warn('User Fingerprinting is no longer supported. This function will be removed in a future release.');
 		};
 
 		/**
 		 * Enable/disable user fingerprinting. User fingerprinting is enabled by default.
-		 * @param bool enable If false, turn off user fingerprinting
 		 */
-		apiMethods.enableUserFingerprint = function(enable) {
-			helpers.warn('enableUserFingerprintSeed is deprecated. Instead add a "userFingerprint" field to the argmap argument of newTracker.');
-			if (!enable) {
-				userFingerprint = '';
-			}
+		apiMethods.enableUserFingerprint = function() {
+			helpers.warn('User Fingerprinting is no longer supported. This function will be removed in a future release.');
 		};
 
 		/**
diff --git a/vagrant/.gitignore b/vagrant/.gitignore
deleted file mode 100644
index 1b4b29ff..00000000
--- a/vagrant/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-.peru
-oss-playbooks
-ansible
diff --git a/vagrant/ansible.hosts b/vagrant/ansible.hosts
deleted file mode 100644
index 588fa08c..00000000
--- a/vagrant/ansible.hosts
+++ /dev/null
@@ -1,2 +0,0 @@
-[vagrant]
-127.0.0.1:2222
diff --git a/vagrant/peru.yaml b/vagrant/peru.yaml
deleted file mode 100644
index e7fdf41c..00000000
--- a/vagrant/peru.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-imports:
-    ansible: ansible
-    ansible_playbooks: oss-playbooks
-
-curl module ansible:
-    # Equivalent of git cloning tags/v1.6.6 but much, much faster
-    url: https://codeload.github.com/ansible/ansible/zip/69d85c22c7475ccf8169b6ec9dee3ee28c92a314
-    unpack: zip
-    export: ansible-69d85c22c7475ccf8169b6ec9dee3ee28c92a314
-
-git module ansible_playbooks:
-    url: https://github.com/snowplow/ansible-playbooks.git
-    # Comment out to fetch a specific rev instead of master:
-    # rev: xxx
diff --git a/vagrant/up.bash b/vagrant/up.bash
deleted file mode 100755
index 7450ae89..00000000
--- a/vagrant/up.bash
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/bin/bash
-set -e
-
-vagrant_dir=/vagrant/vagrant
-bashrc=/home/vagrant/.bashrc
-
-echo "========================================"
-echo "INSTALLING PERU AND ANSIBLE DEPENDENCIES"
-echo "----------------------------------------"
-apt-get update
-apt-get install -y language-pack-en git unzip libyaml-dev python3-pip python-yaml python-paramiko python-jinja2
-
-echo "==============="
-echo "INSTALLING PERU"
-echo "---------------"
-sudo pip3 install peru
-
-echo "======================================="
-echo "CLONING ANSIBLE AND PLAYBOOKS WITH PERU"
-echo "---------------------------------------"
-cd ${vagrant_dir} && peru sync -v
-echo "... done"
-
-env_setup=${vagrant_dir}/ansible/hacking/env-setup
-hosts=${vagrant_dir}/ansible.hosts
-
-echo "==================="
-echo "CONFIGURING ANSIBLE"
-echo "-------------------"
-touch ${bashrc}
-echo "source ${env_setup}" >> ${bashrc}
-echo "export ANSIBLE_HOSTS=${hosts}" >> ${bashrc}
-echo "... done"
-
-echo "=========================================="
-echo "RUNNING PLAYBOOKS WITH ANSIBLE*"
-echo "* no output while each playbook is running"
-echo "------------------------------------------"
-while read pb; do
-    su - -c "source ${env_setup} && ${vagrant_dir}/ansible/bin/ansible-playbook ${vagrant_dir}/${pb} --connection=local --inventory-file=${hosts}" vagrant
-done <${vagrant_dir}/up.playbooks
-
-guidance=${vagrant_dir}/up.guidance
-
-if [ -f ${guidance} ]; then
-    echo "==========="
-    echo "PLEASE READ"
-    echo "-----------"
-    cat $guidance
-fi
diff --git a/vagrant/up.guidance b/vagrant/up.guidance
deleted file mode 100644
index 25078aa2..00000000
--- a/vagrant/up.guidance
+++ /dev/null
@@ -1,6 +0,0 @@
-To get started:
-vagrant ssh
-cd /vagrant
-sudo npm install
-cd core
-sudo npm install
diff --git a/vagrant/up.playbooks b/vagrant/up.playbooks
deleted file mode 100644
index 9e621ab9..00000000
--- a/vagrant/up.playbooks
+++ /dev/null
@@ -1,3 +0,0 @@
-oss-playbooks/java10.yml
-oss-playbooks/nodejs.yml
-oss-playbooks/grunt.yml


Test patch:
diff --git a/tests/functional/detectors.spec.js b/tests/functional/detectors.spec.js
index 2d4a3af8..bdf6b94f 100644
--- a/tests/functional/detectors.spec.js
+++ b/tests/functional/detectors.spec.js
@@ -40,10 +40,6 @@ describe('Detectors test', () => {
     $('body.loaded').waitForExist()
   })
 
-  it('Returns a value for user fingerprint', () => {
-    expect($('#detectSignature').getText()).toBeTruthy()
-  })
-
   it('Returns a value for document dimensions', () => {
     const value = $('#detectDocumentDimensions').getText()
     const [reportedWidth, reportedHeight] = value.split('x')
diff --git a/tests/pages/detectors.html b/tests/pages/detectors.html
index d62f9d2f..4d02b0d5 100644
--- a/tests/pages/detectors.html
+++ b/tests/pages/detectors.html
@@ -10,8 +10,7 @@
 	<p id="hasSessionStorage">sessionStorage here</p>
 	<p id="hasCookies">hasCookies here</p>
 	<p id="detectTimezone">Timezone here</p>
-	<p id="detectSignature">User fingerprint here</p>
-	<p id="detectBrowserFeatures">User fingerprint here</p>
+	<p id="detectBrowserFeatures">Browser features here</p>
 
 	<script src="./detectors.js"></script>
 
diff --git a/tests/scripts/detectors.js b/tests/scripts/detectors.js
index 7add52ff..8222cafc 100644
--- a/tests/scripts/detectors.js
+++ b/tests/scripts/detectors.js
@@ -40,5 +40,4 @@ document.getElementById('localStorageAccessible').innerHTML = detectors.localSto
 document.getElementById('hasSessionStorage').innerHTML = detectors.hasSessionStorage();
 document.getElementById('hasCookies').innerHTML = detectors.hasCookies();
 document.getElementById('detectTimezone').innerHTML = detectors.detectTimezone();
-document.getElementById('detectSignature').innerHTML = detectors.detectSignature();
 document.getElementById('detectBrowserFeatures').innerHTML = JSON.stringify(detectors.detectBrowserFeatures(), undefined, 2);
diff --git a/tests/unit/in_queue.spec.js b/tests/unit/in_queue.spec.js
index c4d54138..524d07f7 100644
--- a/tests/unit/in_queue.spec.js
+++ b/tests/unit/in_queue.spec.js
@@ -111,4 +111,14 @@ describe('InQueueManager', () => {
     ])
     expect(callbackExecuted).toBe(true)
   })
+
+  it('Executing a custom callback that errors should not throw', () => {
+    expect(() => {
+      asyncQueue.push([
+        function() {
+          throw 'caught error'
+        },
+      ])
+    }).not.toThrow()
+  })
 })