http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/e13e8a10/extensions/web-tracker/wab/src/main/webapp/javascript/unomi-tracker.js
----------------------------------------------------------------------
diff --git 
a/extensions/web-tracker/wab/src/main/webapp/javascript/unomi-tracker.js 
b/extensions/web-tracker/wab/src/main/webapp/javascript/unomi-tracker.js
index e264b03..6545d98 100644
--- a/extensions/web-tracker/wab/src/main/webapp/javascript/unomi-tracker.js
+++ b/extensions/web-tracker/wab/src/main/webapp/javascript/unomi-tracker.js
@@ -1,1438 +1,483 @@
 (function(f){if(typeof exports==="object"&&typeof 
module!=="undefined"){module.exports=f()}else if(typeof 
define==="function"&&define.amd){define([],f)}else{var g;if(typeof 
window!=="undefined"){g=window}else if(typeof 
global!=="undefined"){g=global}else if(typeof 
self!=="undefined"){g=self}else{g=this}g.unomiTracker = f()}})(function(){var 
define,module,exports;return (function(){function r(e,n,t){function 
o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof 
require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new 
Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var 
p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return 
o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof 
require&&require,i=0;i<t.length;i++)o(t[i]);return o}return 
r})()({1:[function(require,module,exports){
-// shim for using process in browser
-var process = module.exports = {};
-
-// cached from whatever global is present so that test runners that stub it
-// don't break things.  But we need to wrap it in a try catch in case it is
-// wrapped in strict mode code which doesn't define any globals.  It's inside a
-// function because try/catches deoptimize in certain engines.
-
-var cachedSetTimeout;
-var cachedClearTimeout;
-
-function defaultSetTimout() {
-    throw new Error('setTimeout has not been defined');
-}
-function defaultClearTimeout () {
-    throw new Error('clearTimeout has not been defined');
-}
-(function () {
-    try {
-        if (typeof setTimeout === 'function') {
-            cachedSetTimeout = setTimeout;
-        } else {
-            cachedSetTimeout = defaultSetTimout;
-        }
-    } catch (e) {
-        cachedSetTimeout = defaultSetTimout;
-    }
-    try {
-        if (typeof clearTimeout === 'function') {
-            cachedClearTimeout = clearTimeout;
-        } else {
-            cachedClearTimeout = defaultClearTimeout;
-        }
-    } catch (e) {
-        cachedClearTimeout = defaultClearTimeout;
-    }
-} ())
-function runTimeout(fun) {
-    if (cachedSetTimeout === setTimeout) {
-        //normal enviroments in sane situations
-        return setTimeout(fun, 0);
-    }
-    // if setTimeout wasn't available but was latter defined
-    if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && 
setTimeout) {
-        cachedSetTimeout = setTimeout;
-        return setTimeout(fun, 0);
-    }
-    try {
-        // when when somebody has screwed with setTimeout but no I.E. maddness
-        return cachedSetTimeout(fun, 0);
-    } catch(e){
-        try {
-            // When we are in I.E. but the script has been evaled so I.E. 
doesn't trust the global object when called normally
-            return cachedSetTimeout.call(null, fun, 0);
-        } catch(e){
-            // same as above but when it's a version of I.E. that must have 
the global object for 'this', hopfully our context correct otherwise it will 
throw a global error
-            return cachedSetTimeout.call(this, fun, 0);
-        }
-    }
-
-
-}
-function runClearTimeout(marker) {
-    if (cachedClearTimeout === clearTimeout) {
-        //normal enviroments in sane situations
-        return clearTimeout(marker);
-    }
-    // if clearTimeout wasn't available but was latter defined
-    if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && 
clearTimeout) {
-        cachedClearTimeout = clearTimeout;
-        return clearTimeout(marker);
-    }
-    try {
-        // when when somebody has screwed with setTimeout but no I.E. maddness
-        return cachedClearTimeout(marker);
-    } catch (e){
-        try {
-            // When we are in I.E. but the script has been evaled so I.E. 
doesn't  trust the global object when called normally
-            return cachedClearTimeout.call(null, marker);
-        } catch (e){
-            // same as above but when it's a version of I.E. that must have 
the global object for 'this', hopfully our context correct otherwise it will 
throw a global error.
-            // Some versions of I.E. have different rules for clearTimeout vs 
setTimeout
-            return cachedClearTimeout.call(this, marker);
-        }
-    }
-
-
-
-}
-var queue = [];
-var draining = false;
-var currentQueue;
-var queueIndex = -1;
-
-function cleanUpNextTick() {
-    if (!draining || !currentQueue) {
-        return;
-    }
-    draining = false;
-    if (currentQueue.length) {
-        queue = currentQueue.concat(queue);
-    } else {
-        queueIndex = -1;
-    }
-    if (queue.length) {
-        drainQueue();
-    }
-}
-
-function drainQueue() {
-    if (draining) {
-        return;
-    }
-    var timeout = runTimeout(cleanUpNextTick);
-    draining = true;
-
-    var len = queue.length;
-    while(len) {
-        currentQueue = queue;
-        queue = [];
-        while (++queueIndex < len) {
-            if (currentQueue) {
-                currentQueue[queueIndex].run();
-            }
-        }
-        queueIndex = -1;
-        len = queue.length;
-    }
-    currentQueue = null;
-    draining = false;
-    runClearTimeout(timeout);
-}
-
-process.nextTick = function (fun) {
-    var args = new Array(arguments.length - 1);
-    if (arguments.length > 1) {
-        for (var i = 1; i < arguments.length; i++) {
-            args[i - 1] = arguments[i];
-        }
-    }
-    queue.push(new Item(fun, args));
-    if (queue.length === 1 && !draining) {
-        runTimeout(drainQueue);
-    }
-};
-
-// v8 likes predictible objects
-function Item(fun, array) {
-    this.fun = fun;
-    this.array = array;
-}
-Item.prototype.run = function () {
-    this.fun.apply(null, this.array);
-};
-process.title = 'browser';
-process.browser = true;
-process.env = {};
-process.argv = [];
-process.version = ''; // empty string to avoid regexp issues
-process.versions = {};
-
-function noop() {}
-
-process.on = noop;
-process.addListener = noop;
-process.once = noop;
-process.off = noop;
-process.removeListener = noop;
-process.removeAllListeners = noop;
-process.emit = noop;
-process.prependListener = noop;
-process.prependOnceListener = noop;
-
-process.listeners = function (name) { return [] }
-
-process.binding = function (name) {
-    throw new Error('process.binding is not supported');
-};
-
-process.cwd = function () { return '/' };
-process.chdir = function (dir) {
-    throw new Error('process.chdir is not supported');
-};
-process.umask = function() { return 0; };
-
-},{}],2:[function(require,module,exports){
-(function (setImmediate,clearImmediate){
-var nextTick = require('process/browser.js').nextTick;
-var apply = Function.prototype.apply;
-var slice = Array.prototype.slice;
-var immediateIds = {};
-var nextImmediateId = 0;
-
-// DOM APIs, for completeness
-
-exports.setTimeout = function() {
-  return new Timeout(apply.call(setTimeout, window, arguments), clearTimeout);
-};
-exports.setInterval = function() {
-  return new Timeout(apply.call(setInterval, window, arguments), 
clearInterval);
-};
-exports.clearTimeout =
-exports.clearInterval = function(timeout) { timeout.close(); };
-
-function Timeout(id, clearFn) {
-  this._id = id;
-  this._clearFn = clearFn;
-}
-Timeout.prototype.unref = Timeout.prototype.ref = function() {};
-Timeout.prototype.close = function() {
-  this._clearFn.call(window, this._id);
-};
-
-// Does not start the time, just sets up the members needed.
-exports.enroll = function(item, msecs) {
-  clearTimeout(item._idleTimeoutId);
-  item._idleTimeout = msecs;
-};
-
-exports.unenroll = function(item) {
-  clearTimeout(item._idleTimeoutId);
-  item._idleTimeout = -1;
-};
-
-exports._unrefActive = exports.active = function(item) {
-  clearTimeout(item._idleTimeoutId);
-
-  var msecs = item._idleTimeout;
-  if (msecs >= 0) {
-    item._idleTimeoutId = setTimeout(function onTimeout() {
-      if (item._onTimeout)
-        item._onTimeout();
-    }, msecs);
-  }
-};
-
-// That's not how node.js implements it but the exposed api is the same.
-exports.setImmediate = typeof setImmediate === "function" ? setImmediate : 
function(fn) {
-  var id = nextImmediateId++;
-  var args = arguments.length < 2 ? false : slice.call(arguments, 1);
-
-  immediateIds[id] = true;
-
-  nextTick(function onNextTick() {
-    if (immediateIds[id]) {
-      // fn.call() is faster so we optimize for the common use-case
-      // @see http://jsperf.com/call-apply-segu
-      if (args) {
-        fn.apply(null, args);
-      } else {
-        fn.call(null);
-      }
-      // Prevent ids from leaking
-      exports.clearImmediate(id);
-    }
-  });
-
-  return id;
-};
-
-exports.clearImmediate = typeof clearImmediate === "function" ? clearImmediate 
: function(id) {
-  delete immediateIds[id];
-};
-}).call(this,require("timers").setImmediate,require("timers").clearImmediate)
-},{"process/browser.js":1,"timers":2}],3:[function(require,module,exports){
-'use strict';
-
-/*
- * Module dependencies.
- */
-
-var arity = require('@ndhoule/arity');
-
-var objToString = Object.prototype.toString;
-
-/**
- * Determine if a value is a function.
- *
- * @param {*} val
- * @return {boolean}
- */
-// TODO: Move to lib
-var isFunction = function(val) {
-  return typeof val === 'function';
-};
-
-/**
- * Determine if a value is a number.
- *
- * @param {*} val
- * @return {boolean}
- */
-// TODO: Move to lib
-var isNumber = function(val) {
-  var type = typeof val;
-  return type === 'number' || (type === 'object' && objToString.call(val) === 
'[object Number]');
-};
-
-/**
- * Wrap a function `fn` in a function that will invoke `fn` when invoked `n` or
- * more times.
- *
- * @name after
- * @api public
- * @category Function
- * @param {Number} n The number of
- * @param {Function} fn The function to wrap.
- * @return {Function} A function that will call `fn` after `n` or more
- * invocations.
- * @example
- */
-var after = function after(n, fn) {
-  if (!isNumber(n)) {
-    throw new TypeError('Expected a number but received ' + typeof n);
-  }
-
-  if (!isFunction(fn)) {
-    throw new TypeError('Expected a function but received ' + typeof fn);
-  }
-
-  var callCount = 0;
-
-  return arity(fn.length, function() {
-    callCount += 1;
-
-    if (callCount < n) {
-      return;
-    }
-
-    return fn.apply(this, arguments);
-  });
-};
-
-/*
- * Exports.
- */
-
-module.exports = after;
-
-},{"@ndhoule/arity":4}],4:[function(require,module,exports){
-'use strict';
-
-var objToString = Object.prototype.toString;
-
-/**
- * Determine if a value is a function.
- *
- * @param {*} val
- * @return {boolean}
- */
-// TODO: Move to lib
-var isFunction = function(val) {
-  return typeof val === 'function';
-};
-
-/**
- * Determine if a value is a number.
- *
- * @param {*} val
- * @return {boolean}
- */
-// TODO: Move to lib
-var isNumber = function(val) {
-  var type = typeof val;
-  return type === 'number' || (type === 'object' && objToString.call(val) === 
'[object Number]');
-};
-
- /**
-  * Creates an array of generic, numbered argument names.
-  *
-  * @name createParams
-  * @api private
-  * @param {number} n
-  * @return {Array}
-  * @example
-  * argNames(2);
-  * //=> ['arg1', 'arg2']
-  */
-var createParams = function createParams(n) {
-  var args = [];
-
-  for (var i = 1; i <= n; i += 1) {
-    args.push('arg' + i);
-  }
-
-  return args;
-};
-
- /**
-  * Dynamically construct a wrapper function of `n` arity that.
-  *
-  * If at all possible, prefer a function from the arity wrapper cache above to
-  * avoid allocating a new function at runtime.
-  *
-  * @name createArityWrapper
-  * @api private
-  * @param {number} n
-  * @return {Function(Function)}
-  */
-var createArityWrapper = function createArityWrapper(n) {
-  var paramNames = createParams(n).join(', ');
-  var wrapperBody = ''.concat(
-    '  return function(', paramNames, ') {\n',
-    '    return func.apply(this, arguments);\n',
-    '  };'
-  );
-
-  /* eslint-disable no-new-func */
-  return new Function('func', wrapperBody);
-  /* eslint-enable no-new-func */
-};
-
-// Cache common arity wrappers to avoid constructing them at runtime
-var arityWrapperCache = [
-  /* eslint-disable no-unused-vars */
-  function(fn) {
-    return function() {
-      return fn.apply(this, arguments);
-    };
-  },
-
-  function(fn) {
-    return function(arg1) {
-      return fn.apply(this, arguments);
-    };
-  },
-
-  function(fn) {
-    return function(arg1, arg2) {
-      return fn.apply(this, arguments);
-    };
-  },
-
-  function(fn) {
-    return function(arg1, arg2, arg3) {
-      return fn.apply(this, arguments);
-    };
-  },
-
-  function(fn) {
-    return function(arg1, arg2, arg3, arg4) {
-      return fn.apply(this, arguments);
-    };
-  },
-
-  function(fn) {
-    return function(arg1, arg2, arg3, arg4, arg5) {
-      return fn.apply(this, arguments);
-    };
-  }
-  /* eslint-enable no-unused-vars */
-];
-
-/**
- * Takes a function and an [arity](https://en.wikipedia.org/wiki/Arity) `n`, 
and returns a new
- * function that expects `n` arguments.
- *
- * @name arity
- * @api public
- * @category Function
- * @see {@link curry}
- * @param {Number} n The desired arity of the returned function.
- * @param {Function} fn The function to wrap.
- * @return {Function} A function of n arity, wrapping `fn`.
- * @example
- * var add = function(a, b) {
- *   return a + b;
- * };
- *
- * // Check the number of arguments this function expects by accessing 
`.length`:
- * add.length;
- * //=> 2
- *
- * var unaryAdd = arity(1, add);
- * unaryAdd.length;
- * //=> 1
- */
-var arity = function arity(n, func) {
-  if (!isFunction(func)) {
-    throw new TypeError('Expected a function but got ' + typeof func);
-  }
-
-  n = Math.max(isNumber(n) ? n : 0, 0);
-
-  if (!arityWrapperCache[n]) {
-    arityWrapperCache[n] = createArityWrapper(n);
-  }
-
-  return arityWrapperCache[n](func);
-};
-
-/*
- * Exports.
- */
-
-module.exports = arity;
-
-},{}],5:[function(require,module,exports){
-'use strict';
-
-/*
- * Module dependencies.
- */
-
-var type = require('component-type');
-
-/**
- * Deeply clone an object.
- *
- * @param {*} obj Any object.
- */
-
-var clone = function clone(obj) {
-  var t = type(obj);
-
-  if (t === 'object') {
-    var copy = {};
-    for (var key in obj) {
-      if (obj.hasOwnProperty(key)) {
-        copy[key] = clone(obj[key]);
-      }
-    }
-    return copy;
-  }
-
-  if (t === 'array') {
-    var copy = new Array(obj.length);
-    for (var i = 0, l = obj.length; i < l; i++) {
-      copy[i] = clone(obj[i]);
-    }
-    return copy;
-  }
-
-  if (t === 'regexp') {
-    // from millermedeiros/amd-utils - MIT
-    var flags = '';
-    flags += obj.multiline ? 'm' : '';
-    flags += obj.global ? 'g' : '';
-    flags += obj.ignoreCase ? 'i' : '';
-    return new RegExp(obj.source, flags);
-  }
-
-  if (t === 'date') {
-    return new Date(obj.getTime());
-  }
-
-  // string, number, boolean, etc.
-  return obj;
-};
-
-/*
- * Exports.
- */
-
-module.exports = clone;
-
-},{"component-type":58}],6:[function(require,module,exports){
-'use strict';
-
-/*
- * Module dependencies.
- */
-
-var drop = require('@ndhoule/drop');
-var rest = require('@ndhoule/rest');
-
-var has = Object.prototype.hasOwnProperty;
-var objToString = Object.prototype.toString;
-
-/**
- * Returns `true` if a value is an object, otherwise `false`.
- *
- * @name isObject
- * @api private
- * @param {*} val The value to test.
- * @return {boolean}
- */
-// TODO: Move to a library
-var isObject = function isObject(value) {
-  return Boolean(value) && typeof value === 'object';
-};
-
-/**
- * Returns `true` if a value is a plain object, otherwise `false`.
- *
- * @name isPlainObject
- * @api private
- * @param {*} val The value to test.
- * @return {boolean}
- */
-// TODO: Move to a library
-var isPlainObject = function isPlainObject(value) {
-  return Boolean(value) && objToString.call(value) === '[object Object]';
-};
-
-/**
- * Assigns a key-value pair to a target object when the value assigned is 
owned,
- * and where target[key] is undefined.
- *
- * @name shallowCombiner
- * @api private
- * @param {Object} target
- * @param {Object} source
- * @param {*} value
- * @param {string} key
- */
-var shallowCombiner = function shallowCombiner(target, source, value, key) {
-  if (has.call(source, key) && target[key] === undefined) {
-    target[key] = value;
-  }
-  return source;
-};
-
-/**
- * Assigns a key-value pair to a target object when the value assigned is 
owned,
- * and where target[key] is undefined; also merges objects recursively.
- *
- * @name deepCombiner
- * @api private
- * @param {Object} target
- * @param {Object} source
- * @param {*} value
- * @param {string} key
- * @return {Object}
- */
-var deepCombiner = function(target, source, value, key) {
-  if (has.call(source, key)) {
-    if (isPlainObject(target[key]) && isPlainObject(value)) {
-        target[key] = defaultsDeep(target[key], value);
-    } else if (target[key] === undefined) {
-        target[key] = value;
-    }
-  }
-
-  return source;
-};
-
-/**
- * TODO: Document
- *
- * @name defaultsWith
- * @api private
- * @param {Function} combiner
- * @param {Object} target
- * @param {...Object} sources
- * @return {Object} Return the input `target`.
- */
-var defaultsWith = function(combiner, target /*, ...sources */) {
-  if (!isObject(target)) {
-    return target;
-  }
-
-  combiner = combiner || shallowCombiner;
-  var sources = drop(2, arguments);
-
-  for (var i = 0; i < sources.length; i += 1) {
-    for (var key in sources[i]) {
-      combiner(target, sources[i], sources[i][key], key);
-    }
-  }
-
-  return target;
-};
-
-/**
- * Copies owned, enumerable properties from a source object(s) to a target
- * object when the value of that property on the source object is `undefined`.
- * Recurses on objects.
- *
- * @name defaultsDeep
- * @api public
- * @param {Object} target
- * @param {...Object} sources
- * @return {Object} The input `target`.
- */
-var defaultsDeep = function defaultsDeep(target /*, sources */) {
-  // TODO: Replace with `partial` call?
-  return defaultsWith.apply(null, [deepCombiner, 
target].concat(rest(arguments)));
-};
-
-/**
- * Copies owned, enumerable properties from a source object(s) to a target
- * object when the value of that property on the source object is `undefined`.
- *
- * @name defaults
- * @api public
- * @param {Object} target
- * @param {...Object} sources
- * @return {Object}
- * @example
- * var a = { a: 1 };
- * var b = { a: 2, b: 2 };
- *
- * defaults(a, b);
- * console.log(a); //=> { a: 1, b: 2 }
- */
-var defaults = function(target /*, ...sources */) {
-  // TODO: Replace with `partial` call?
-  return defaultsWith.apply(null, [null, target].concat(rest(arguments)));
-};
-
-/*
- * Exports.
- */
-
-module.exports = defaults;
-module.exports.deep = defaultsDeep;
-
-},{"@ndhoule/drop":7,"@ndhoule/rest":16}],7:[function(require,module,exports){
-'use strict';
-
-var max = Math.max;
-
-/**
- * Produce a new array composed of all but the first `n` elements of an input 
`collection`.
- *
- * @name drop
- * @api public
- * @param {number} count The number of elements to drop.
- * @param {Array} collection The collection to iterate over.
- * @return {Array} A new array containing all but the first element from 
`collection`.
- * @example
- * drop(0, [1, 2, 3]); // => [1, 2, 3]
- * drop(1, [1, 2, 3]); // => [2, 3]
- * drop(2, [1, 2, 3]); // => [3]
- * drop(3, [1, 2, 3]); // => []
- * drop(4, [1, 2, 3]); // => []
- */
-var drop = function drop(count, collection) {
-  var length = collection ? collection.length : 0;
-
-  if (!length) {
-    return [];
-  }
-
-  // Preallocating an array *significantly* boosts performance when dealing 
with
-  // `arguments` objects on v8. For a summary, see:
-  // 
https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments
-  var toDrop = max(Number(count) || 0, 0);
-  var resultsLength = max(length - toDrop, 0);
-  var results = new Array(resultsLength);
-
-  for (var i = 0; i < resultsLength; i += 1) {
-    results[i] = collection[i + toDrop];
-  }
-
-  return results;
-};
-
-/*
- * Exports.
- */
-
-module.exports = drop;
-
-},{}],8:[function(require,module,exports){
-'use strict';
-
-/*
- * Module dependencies.
- */
-
-var keys = require('@ndhoule/keys');
-
-var objToString = Object.prototype.toString;
-
-/**
- * Tests if a value is a number.
- *
- * @name isNumber
- * @api private
- * @param {*} val The value to test.
- * @return {boolean} Returns `true` if `val` is a number, otherwise `false`.
- */
-// TODO: Move to library
-var isNumber = function isNumber(val) {
-  var type = typeof val;
-  return type === 'number' || (type === 'object' && objToString.call(val) === 
'[object Number]');
-};
-
-/**
- * Tests if a value is an array.
- *
- * @name isArray
- * @api private
- * @param {*} val The value to test.
- * @return {boolean} Returns `true` if the value is an array, otherwise 
`false`.
- */
-// TODO: Move to library
-var isArray = typeof Array.isArray === 'function' ? Array.isArray : function 
isArray(val) {
-  return objToString.call(val) === '[object Array]';
-};
-
-/**
- * Tests if a value is array-like. Array-like means the value is not a 
function and has a numeric
- * `.length` property.
- *
- * @name isArrayLike
- * @api private
- * @param {*} val
- * @return {boolean}
- */
-// TODO: Move to library
-var isArrayLike = function isArrayLike(val) {
-  return val != null && (isArray(val) || (val !== 'function' && 
isNumber(val.length)));
-};
-
-/**
- * Internal implementation of `each`. Works on arrays and array-like data 
structures.
- *
- * @name arrayEach
- * @api private
- * @param {Function(value, key, collection)} iterator The function to invoke 
per iteration.
- * @param {Array} array The array(-like) structure to iterate over.
- * @return {undefined}
- */
-var arrayEach = function arrayEach(iterator, array) {
-  for (var i = 0; i < array.length; i += 1) {
-    // Break iteration early if `iterator` returns `false`
-    if (iterator(array[i], i, array) === false) {
-      break;
-    }
-  }
-};
-
-/**
- * Internal implementation of `each`. Works on objects.
- *
- * @name baseEach
- * @api private
- * @param {Function(value, key, collection)} iterator The function to invoke 
per iteration.
- * @param {Object} object The object to iterate over.
- * @return {undefined}
- */
-var baseEach = function baseEach(iterator, object) {
-  var ks = keys(object);
-
-  for (var i = 0; i < ks.length; i += 1) {
-    // Break iteration early if `iterator` returns `false`
-    if (iterator(object[ks[i]], ks[i], object) === false) {
-      break;
-    }
-  }
-};
-
-/**
- * Iterate over an input collection, invoking an `iterator` function for each 
element in the
- * collection and passing to it three arguments: `(value, index, collection)`. 
The `iterator`
- * function can end iteration early by returning `false`.
- *
- * @name each
- * @api public
- * @param {Function(value, key, collection)} iterator The function to invoke 
per iteration.
- * @param {Array|Object|string} collection The collection to iterate over.
- * @return {undefined} Because `each` is run only for side effects, always 
returns `undefined`.
- * @example
- * var log = console.log.bind(console);
- *
- * each(log, ['a', 'b', 'c']);
- * //-> 'a', 0, ['a', 'b', 'c']
- * //-> 'b', 1, ['a', 'b', 'c']
- * //-> 'c', 2, ['a', 'b', 'c']
- * //=> undefined
- *
- * each(log, 'tim');
- * //-> 't', 2, 'tim'
- * //-> 'i', 1, 'tim'
- * //-> 'm', 0, 'tim'
- * //=> undefined
- *
- * // Note: Iteration order not guaranteed across environments
- * each(log, { name: 'tim', occupation: 'enchanter' });
- * //-> 'tim', 'name', { name: 'tim', occupation: 'enchanter' }
- * //-> 'enchanter', 'occupation', { name: 'tim', occupation: 'enchanter' }
- * //=> undefined
- */
-var each = function each(iterator, collection) {
-  return (isArrayLike(collection) ? arrayEach : baseEach).call(this, iterator, 
collection);
-};
-
-/*
- * Exports.
- */
-
-module.exports = each;
-
-},{"@ndhoule/keys":13}],9:[function(require,module,exports){
-'use strict';
-
-/*
- * Module dependencies.
- */
-
-var each = require('@ndhoule/each');
-
-/**
- * Check if a predicate function returns `true` for all values in a 
`collection`.
- * Checks owned, enumerable values and exits early when `predicate` returns
- * `false`.
- *
- * @name every
- * @param {Function} predicate The function used to test values.
- * @param {Array|Object|string} collection The collection to search.
- * @return {boolean} True if all values passes the predicate test, otherwise 
false.
- * @example
- * var isEven = function(num) { return num % 2 === 0; };
- *
- * every(isEven, []); // => true
- * every(isEven, [1, 2]); // => false
- * every(isEven, [2, 4, 6]); // => true
- */
-var every = function every(predicate, collection) {
-  if (typeof predicate !== 'function') {
-    throw new TypeError('`predicate` must be a function but was a ' + typeof 
predicate);
-  }
-
-  var result = true;
-
-  each(function(val, key, collection) {
-    result = !!predicate(val, key, collection);
-
-    // Exit early
-    if (!result) {
-      return false;
-    }
-  }, collection);
-
-  return result;
-};
-
-/*
- * Exports.
- */
-
-module.exports = every;
-
-},{"@ndhoule/each":8}],10:[function(require,module,exports){
-'use strict';
-
-var has = Object.prototype.hasOwnProperty;
-
-/**
- * Copy the properties of one or more `objects` onto a destination object. 
Input objects are iterated over
- * in left-to-right order, so duplicate properties on later objects will 
overwrite those from
- * erevious ones. Only enumerable and own properties of the input objects are 
copied onto the
- * resulting object.
- *
- * @name extend
- * @api public
- * @category Object
- * @param {Object} dest The destination object.
- * @param {...Object} sources The source objects.
- * @return {Object} `dest`, extended with the properties of all `sources`.
- * @example
- * var a = { a: 'a' };
- * var b = { b: 'b' };
- * var c = { c: 'c' };
- *
- * extend(a, b, c);
- * //=> { a: 'a', b: 'b', c: 'c' };
- */
-var extend = function extend(dest /*, sources */) {
-  var sources = Array.prototype.slice.call(arguments, 1);
-
-  for (var i = 0; i < sources.length; i += 1) {
-    for (var key in sources[i]) {
-      if (has.call(sources[i], key)) {
-        dest[key] = sources[i][key];
-      }
-    }
-  }
-
-  return dest;
-};
-
-/*
- * Exports.
- */
-
-module.exports = extend;
-
-},{}],11:[function(require,module,exports){
-'use strict';
-
-/*
- * Module dependencies.
- */
-
-var each = require('@ndhoule/each');
-
-/**
- * Reduces all the values in a collection down into a single value. Does so by 
iterating through the
- * collection from left to right, repeatedly calling an `iterator` function 
and passing to it four
- * arguments: `(accumulator, value, index, collection)`.
- *
- * Returns the final return value of the `iterator` function.
- *
- * @name foldl
- * @api public
- * @param {Function} iterator The function to invoke per iteration.
- * @param {*} accumulator The initial accumulator value, passed to the first 
invocation of `iterator`.
- * @param {Array|Object} collection The collection to iterate over.
- * @return {*} The return value of the final call to `iterator`.
- * @example
- * foldl(function(total, n) {
- *   return total + n;
- * }, 0, [1, 2, 3]);
- * //=> 6
- *
- * var phonebook = { bob: '555-111-2345', tim: '655-222-6789', sheila: 
'655-333-1298' };
- *
- * foldl(function(results, phoneNumber) {
- *  if (phoneNumber[0] === '6') {
- *    return results.concat(phoneNumber);
- *  }
- *  return results;
- * }, [], phonebook);
- * // => ['655-222-6789', '655-333-1298']
- */
-var foldl = function foldl(iterator, accumulator, collection) {
-  if (typeof iterator !== 'function') {
-    throw new TypeError('Expected a function but received a ' + typeof 
iterator);
-  }
-
-  each(function(val, i, collection) {
-    accumulator = iterator(accumulator, val, i, collection);
-  }, collection);
-
-  return accumulator;
-};
-
-/*
- * Exports.
- */
-
-module.exports = foldl;
-
-},{"@ndhoule/each":8}],12:[function(require,module,exports){
 'use strict';
 
 /*
  * Module dependencies.
  */
 
-var each = require('@ndhoule/each');
+var arity = require('@ndhoule/arity');
 
-var strIndexOf = String.prototype.indexOf;
+var objToString = Object.prototype.toString;
 
 /**
- * Object.is/sameValueZero polyfill.
+ * Determine if a value is a function.
  *
- * @api private
- * @param {*} value1
- * @param {*} value2
+ * @param {*} val
  * @return {boolean}
  */
-// TODO: Move to library
-var sameValueZero = function sameValueZero(value1, value2) {
-  // Normal values and check for 0 / -0
-  if (value1 === value2) {
-    return value1 !== 0 || 1 / value1 === 1 / value2;
-  }
-  // NaN
-  return value1 !== value1 && value2 !== value2;
+// TODO: Move to lib
+var isFunction = function(val) {
+  return typeof val === 'function';
 };
 
 /**
- * Searches a given `collection` for a value, returning true if the collection
- * contains the value and false otherwise. Can search strings, arrays, and
- * objects.
+ * Determine if a value is a number.
  *
- * @name includes
- * @api public
- * @param {*} searchElement The element to search for.
- * @param {Object|Array|string} collection The collection to search.
+ * @param {*} val
  * @return {boolean}
- * @example
- * includes(2, [1, 2, 3]);
- * //=> true
- *
- * includes(4, [1, 2, 3]);
- * //=> false
- *
- * includes(2, { a: 1, b: 2, c: 3 });
- * //=> true
- *
- * includes('a', { a: 1, b: 2, c: 3 });
- * //=> false
- *
- * includes('abc', 'xyzabc opq');
- * //=> true
+ */
+// TODO: Move to lib
+var isNumber = function(val) {
+  var type = typeof val;
+  return type === 'number' || (type === 'object' && objToString.call(val) === 
'[object Number]');
+};
+
+/**
+ * Wrap a function `fn` in a function that will invoke `fn` when invoked `n` or
+ * more times.
  *
- * includes('nope', 'xyzabc opq');
- * //=> false
+ * @name after
+ * @api public
+ * @category Function
+ * @param {Number} n The number of
+ * @param {Function} fn The function to wrap.
+ * @return {Function} A function that will call `fn` after `n` or more
+ * invocations.
+ * @example
  */
-var includes = function includes(searchElement, collection) {
-  var found = false;
+var after = function after(n, fn) {
+  if (!isNumber(n)) {
+    throw new TypeError('Expected a number but received ' + typeof n);
+  }
 
-  // Delegate to String.prototype.indexOf when `collection` is a string
-  if (typeof collection === 'string') {
-    return strIndexOf.call(collection, searchElement) !== -1;
+  if (!isFunction(fn)) {
+    throw new TypeError('Expected a function but received ' + typeof fn);
   }
 
-  // Iterate through enumerable/own array elements and object properties.
-  each(function(value) {
-    if (sameValueZero(value, searchElement)) {
-      found = true;
-      // Exit iteration early when found
-      return false;
+  var callCount = 0;
+
+  return arity(fn.length, function() {
+    callCount += 1;
+
+    if (callCount < n) {
+      return;
     }
-  }, collection);
 
-  return found;
+    return fn.apply(this, arguments);
+  });
 };
 
 /*
  * Exports.
  */
 
-module.exports = includes;
+module.exports = after;
 
-},{"@ndhoule/each":8}],13:[function(require,module,exports){
+},{"@ndhoule/arity":2}],2:[function(require,module,exports){
 'use strict';
 
-var hop = Object.prototype.hasOwnProperty;
-var strCharAt = String.prototype.charAt;
-var toStr = Object.prototype.toString;
-
-/**
- * Returns the character at a given index.
- *
- * @param {string} str
- * @param {number} index
- * @return {string|undefined}
- */
-// TODO: Move to a library
-var charAt = function(str, index) {
-  return strCharAt.call(str, index);
-};
+var objToString = Object.prototype.toString;
 
 /**
- * hasOwnProperty, wrapped as a function.
+ * Determine if a value is a function.
  *
- * @name has
- * @api private
- * @param {*} context
- * @param {string|number} prop
+ * @param {*} val
  * @return {boolean}
  */
-
-// TODO: Move to a library
-var has = function has(context, prop) {
-  return hop.call(context, prop);
+// TODO: Move to lib
+var isFunction = function(val) {
+  return typeof val === 'function';
 };
 
 /**
- * Returns true if a value is a string, otherwise false.
+ * Determine if a value is a number.
  *
- * @name isString
- * @api private
  * @param {*} val
  * @return {boolean}
  */
-
-// TODO: Move to a library
-var isString = function isString(val) {
-  return toStr.call(val) === '[object String]';
+// TODO: Move to lib
+var isNumber = function(val) {
+  var type = typeof val;
+  return type === 'number' || (type === 'object' && objToString.call(val) === 
'[object Number]');
 };
 
-/**
- * Returns true if a value is array-like, otherwise false. Array-like means a
- * value is not null, undefined, or a function, and has a numeric `length`
- * property.
- *
- * @name isArrayLike
- * @api private
- * @param {*} val
- * @return {boolean}
- */
-// TODO: Move to a library
-var isArrayLike = function isArrayLike(val) {
-  return val != null && (typeof val !== 'function' && typeof val.length === 
'number');
+ /**
+  * Creates an array of generic, numbered argument names.
+  *
+  * @name createParams
+  * @api private
+  * @param {number} n
+  * @return {Array}
+  * @example
+  * argNames(2);
+  * //=> ['arg1', 'arg2']
+  */
+var createParams = function createParams(n) {
+  var args = [];
+
+  for (var i = 1; i <= n; i += 1) {
+    args.push('arg' + i);
+  }
+
+  return args;
 };
 
+ /**
+  * Dynamically construct a wrapper function of `n` arity that.
+  *
+  * If at all possible, prefer a function from the arity wrapper cache above to
+  * avoid allocating a new function at runtime.
+  *
+  * @name createArityWrapper
+  * @api private
+  * @param {number} n
+  * @return {Function(Function)}
+  */
+var createArityWrapper = function createArityWrapper(n) {
+  var paramNames = createParams(n).join(', ');
+  var wrapperBody = ''.concat(
+    '  return function(', paramNames, ') {\n',
+    '    return func.apply(this, arguments);\n',
+    '  };'
+  );
 
-/**
- * indexKeys
- *
- * @name indexKeys
- * @api private
- * @param {} target
- * @param {Function} pred
- * @return {Array}
- */
-var indexKeys = function indexKeys(target, pred) {
-  pred = pred || has;
+  /* eslint-disable no-new-func */
+  return new Function('func', wrapperBody);
+  /* eslint-enable no-new-func */
+};
 
-  var results = [];
+// Cache common arity wrappers to avoid constructing them at runtime
+var arityWrapperCache = [
+  /* eslint-disable no-unused-vars */
+  function(fn) {
+    return function() {
+      return fn.apply(this, arguments);
+    };
+  },
 
-  for (var i = 0, len = target.length; i < len; i += 1) {
-    if (pred(target, i)) {
-      results.push(String(i));
-    }
-  }
+  function(fn) {
+    return function(arg1) {
+      return fn.apply(this, arguments);
+    };
+  },
 
-  return results;
-};
+  function(fn) {
+    return function(arg1, arg2) {
+      return fn.apply(this, arguments);
+    };
+  },
 
-/**
- * Returns an array of an object's owned keys.
- *
- * @name objectKeys
- * @api private
- * @param {*} target
- * @param {Function} pred Predicate function used to include/exclude values 
from
- * the resulting array.
- * @return {Array}
- */
-var objectKeys = function objectKeys(target, pred) {
-  pred = pred || has;
+  function(fn) {
+    return function(arg1, arg2, arg3) {
+      return fn.apply(this, arguments);
+    };
+  },
 
-  var results = [];
+  function(fn) {
+    return function(arg1, arg2, arg3, arg4) {
+      return fn.apply(this, arguments);
+    };
+  },
 
-  for (var key in target) {
-    if (pred(target, key)) {
-      results.push(String(key));
-    }
+  function(fn) {
+    return function(arg1, arg2, arg3, arg4, arg5) {
+      return fn.apply(this, arguments);
+    };
   }
-
-  return results;
-};
+  /* eslint-enable no-unused-vars */
+];
 
 /**
- * Creates an array composed of all keys on the input object. Ignores any 
non-enumerable properties.
- * More permissive than the native `Object.keys` function (non-objects will 
not throw errors).
+ * Takes a function and an [arity](https://en.wikipedia.org/wiki/Arity) `n`, 
and returns a new
+ * function that expects `n` arguments.
  *
- * @name keys
+ * @name arity
  * @api public
- * @category Object
- * @param {Object} source The value to retrieve keys from.
- * @return {Array} An array containing all the input `source`'s keys.
+ * @category Function
+ * @see {@link curry}
+ * @param {Number} n The desired arity of the returned function.
+ * @param {Function} fn The function to wrap.
+ * @return {Function} A function of n arity, wrapping `fn`.
  * @example
- * keys({ likes: 'avocado', hates: 'pineapple' });
- * //=> ['likes', 'pineapple'];
- *
- * // Ignores non-enumerable properties
- * var hasHiddenKey = { name: 'Tim' };
- * Object.defineProperty(hasHiddenKey, 'hidden', {
- *   value: 'i am not enumerable!',
- *   enumerable: false
- * })
- * keys(hasHiddenKey);
- * //=> ['name'];
+ * var add = function(a, b) {
+ *   return a + b;
+ * };
  *
- * // Works on arrays
- * keys(['a', 'b', 'c']);
- * //=> ['0', '1', '2']
+ * // Check the number of arguments this function expects by accessing 
`.length`:
+ * add.length;
+ * //=> 2
  *
- * // Skips unpopulated indices in sparse arrays
- * var arr = [1];
- * arr[4] = 4;
- * keys(arr);
- * //=> ['0', '4']
+ * var unaryAdd = arity(1, add);
+ * unaryAdd.length;
+ * //=> 1
  */
-var keys = function keys(source) {
-  if (source == null) {
-    return [];
+var arity = function arity(n, func) {
+  if (!isFunction(func)) {
+    throw new TypeError('Expected a function but got ' + typeof func);
   }
 
-  // IE6-8 compatibility (string)
-  if (isString(source)) {
-    return indexKeys(source, charAt);
-  }
+  n = Math.max(isNumber(n) ? n : 0, 0);
 
-  // IE6-8 compatibility (arguments)
-  if (isArrayLike(source)) {
-    return indexKeys(source, has);
+  if (!arityWrapperCache[n]) {
+    arityWrapperCache[n] = createArityWrapper(n);
   }
 
-  return objectKeys(source);
+  return arityWrapperCache[n](func);
 };
 
 /*
  * Exports.
  */
 
-module.exports = keys;
+module.exports = arity;
 
-},{}],14:[function(require,module,exports){
+},{}],3:[function(require,module,exports){
 'use strict';
 
 /*
  * Module dependencies.
  */
 
-var each = require('@ndhoule/each');
+var type = require('component-type');
 
 /**
- * Produce a new array by passing each value in the input `collection` through 
a transformative
- * `iterator` function. The `iterator` function is passed three arguments:
- * `(value, index, collection)`.
- *
- * @name map
- * @api public
- * @param {Function} iterator The transformer function to invoke per iteration.
- * @param {Array} collection The collection to iterate over.
- * @return {Array} A new array containing the results of each `iterator` 
invocation.
- * @example
- * var square = function(x) { return x * x; };
+ * Deeply clone an object.
  *
- * map(square, [1, 2, 3]);
- * //=> [1, 4, 9]
+ * @param {*} obj Any object.
  */
-var map = function map(iterator, collection) {
-  if (typeof iterator !== 'function') {
-    throw new TypeError('Expected a function but received a ' + typeof 
iterator);
+
+var clone = function clone(obj) {
+  var t = type(obj);
+
+  if (t === 'object') {
+    var copy = {};
+    for (var key in obj) {
+      if (obj.hasOwnProperty(key)) {
+        copy[key] = clone(obj[key]);
+      }
+    }
+    return copy;
   }
 
-  var result = [];
+  if (t === 'array') {
+    var copy = new Array(obj.length);
+    for (var i = 0, l = obj.length; i < l; i++) {
+      copy[i] = clone(obj[i]);
+    }
+    return copy;
+  }
 
-  each(function(val, i, collection) {
-    result.push(iterator(val, i, collection));
-  }, collection);
+  if (t === 'regexp') {
+    // from millermedeiros/amd-utils - MIT
+    var flags = '';
+    flags += obj.multiline ? 'm' : '';
+    flags += obj.global ? 'g' : '';
+    flags += obj.ignoreCase ? 'i' : '';
+    return new RegExp(obj.source, flags);
+  }
 
-  return result;
+  if (t === 'date') {
+    return new Date(obj.getTime());
+  }
+
+  // string, number, boolean, etc.
+  return obj;
 };
 
 /*
  * Exports.
  */
 
-module.exports = map;
+module.exports = clone;
 
-},{"@ndhoule/each":8}],15:[function(require,module,exports){
+},{"component-type":53}],4:[function(require,module,exports){
 'use strict';
 
+/*
+ * Module dependencies.
+ */
+
+var drop = require('@ndhoule/drop');
+var rest = require('@ndhoule/rest');
+
+var has = Object.prototype.hasOwnProperty;
 var objToString = Object.prototype.toString;
 
-// TODO: Move to lib
-var existy = function(val) {
-  return val != null;
+/**
+ * Returns `true` if a value is an object, otherwise `false`.
+ *
+ * @name isObject
+ * @api private
+ * @param {*} val The value to test.
+ * @return {boolean}
+ */
+// TODO: Move to a library
+var isObject = function isObject(value) {
+  return Boolean(value) && typeof value === 'object';
 };
 
-// TODO: Move to lib
-var isArray = function(val) {
-  return objToString.call(val) === '[object Array]';
+/**
+ * Returns `true` if a value is a plain object, otherwise `false`.
+ *
+ * @name isPlainObject
+ * @api private
+ * @param {*} val The value to test.
+ * @return {boolean}
+ */
+// TODO: Move to a library
+var isPlainObject = function isPlainObject(value) {
+  return Boolean(value) && objToString.call(value) === '[object Object]';
 };
 
-// TODO: Move to lib
-var isString = function(val) {
-   return typeof val === 'string' || objToString.call(val) === '[object 
String]';
+/**
+ * Assigns a key-value pair to a target object when the value assigned is 
owned,
+ * and where target[key] is undefined.
+ *
+ * @name shallowCombiner
+ * @api private
+ * @param {Object} target
+ * @param {Object} source
+ * @param {*} value
+ * @param {string} key
+ */
+var shallowCombiner = function shallowCombiner(target, source, value, key) {
+  if (has.call(source, key) && target[key] === undefined) {
+    target[key] = value;
+  }
+  return source;
 };
 
-// TODO: Move to lib
-var isObject = function(val) {
-  return val != null && typeof val === 'object';
+/**
+ * Assigns a key-value pair to a target object when the value assigned is 
owned,
+ * and where target[key] is undefined; also merges objects recursively.
+ *
+ * @name deepCombiner
+ * @api private
+ * @param {Object} target
+ * @param {Object} source
+ * @param {*} value
+ * @param {string} key
+ * @return {Object}
+ */
+var deepCombiner = function(target, source, value, key) {
+  if (has.call(source, key)) {
+    if (isPlainObject(target[key]) && isPlainObject(value)) {
+        target[key] = defaultsDeep(target[key], value);
+    } else if (target[key] === undefined) {
+        target[key] = value;
+    }
+  }
+
+  return source;
 };
 
 /**
- * Returns a copy of the new `object` containing only the specified properties.
- *
- * @name pick
- * @api public
- * @param {string|string[]} props The property or properties to keep.
- * @param {Object} object The object to iterate over.
- * @return {Object} A new object containing only the specified properties from 
`object`.
- * @example
- * var person = { name: 'Tim', occupation: 'enchanter', fears: 'rabbits' };
- *
- * pick('name', person);
- * //=> { name: 'Tim' }
+ * TODO: Document
  *
- * pick(['name', 'fears'], person);
- * //=> { name: 'Tim', fears: 'rabbits' }
+ * @name defaultsWith
+ * @api private
+ * @param {Function} combiner
+ * @param {Object} target
+ * @param {...Object} sources
+ * @return {Object} Return the input `target`.
  */
-var pick = function pick(props, object) {
-  if (!existy(object) || !isObject(object)) {
-    return {};
+var defaultsWith = function(combiner, target /*, ...sources */) {
+  if (!isObject(target)) {
+    return target;
   }
 
-  if (isString(props)) {
-    props = [props];
-  }
+  combiner = combiner || shallowCombiner;
+  var sources = drop(2, arguments);
 
-  if (!isArray(props)) {
-    props = [];
+  for (var i = 0; i < sources.length; i += 1) {
+    for (var key in sources[i]) {
+      combiner(target, sources[i], sources[i][key], key);
+    }
   }
 
-  var result = {};
+  return target;
+};
 
-  for (var i = 0; i < props.length; i += 1) {
-    if (isString(props[i]) && props[i] in object) {
-      result[props[i]] = object[props[i]];
-    }
-  }
+/**
+ * Copies owned, enumerable properties from a source object(s) to a target
+ * object when the value of that property on the source object is `undefined`.
+ * Recurses on objects.
+ *
+ * @name defaultsDeep
+ * @api public
+ * @param {Object} target
+ * @param {...Object} sources
+ * @return {Object} The input `target`.
+ */
+var defaultsDeep = function defaultsDeep(target /*, sources */) {
+  // TODO: Replace with `partial` call?
+  return defaultsWith.apply(null, [deepCombiner, 
target].concat(rest(arguments)));
+};
 
-  return result;
+/**
+ * Copies owned, enumerable properties from a source object(s) to a target
+ * object when the value of that property on the source object is `undefined`.
+ *
+ * @name defaults
+ * @api public
+ * @param {Object} target
+ * @param {...Object} sources
+ * @return {Object}
+ * @example
+ * var a = { a: 1 };
+ * var b = { a: 2, b: 2 };
+ *
+ * defaults(a, b);
+ * console.log(a); //=> { a: 1, b: 2 }
+ */
+var defaults = function(target /*, ...sources */) {
+  // TODO: Replace with `partial` call?
+  return defaultsWith.apply(null, [null, target].concat(rest(arguments)));
 };
 
 /*
  * Exports.
  */
 
-module.exports = pick;
+module.exports = defaults;
+module.exports.deep = defaultsDeep;
 
-},{}],16:[function(require,module,exports){
+},{"@ndhoule/drop":5,"@ndhoule/rest":14}],5:[function(require,module,exports){
 'use strict';
 
 var max = Math.max;
 
 /**
- * Produce a new array by passing each value in the input `collection` through 
a transformative
- * `iterator` function. The `iterator` function is passed three arguments:
- * `(value, index, collection)`.
+ * Produce a new array composed of all but the first `n` elements of an input 
`collection`.
  *
- * @name rest
+ * @name drop
  * @api public
+ * @param {number} count The number of elements to drop.
  * @param {Array} collection The collection to iterate over.
  * @return {Array} A new array containing all but the first element from 
`collection`.
  * @example
- * rest([1, 2, 3]); // => [2, 3]
+ * drop(0, [1, 2, 3]); // => [1, 2, 3]
+ * drop(1, [1, 2, 3]); // => [2, 3]
+ * drop(2, [1, 2, 3]); // => [3]
+ * drop(3, [1, 2, 3]); // => []
+ * drop(4, [1, 2, 3]); // => []
  */
-var rest = function rest(collection) {
-  if (collection == null || !collection.length) {
+var drop = function drop(count, collection) {
+  var length = collection ? collection.length : 0;
+
+  if (!length) {
     return [];
   }
 
   // Preallocating an array *significantly* boosts performance when dealing 
with
   // `arguments` objects on v8. For a summary, see:
   // 
https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments
-  var results = new Array(max(collection.length - 2, 0));
+  var toDrop = max(Number(count) || 0, 0);
+  var resultsLength = max(length - toDrop, 0);
+  var results = new Array(resultsLength);
 
-  for (var i = 1; i < collection.length; i += 1) {
-    results[i - 1] = collection[i];
+  for (var i = 0; i < resultsLength; i += 1) {
+    results[i] = collection[i + toDrop];
   }
 
   return results;
@@ -1442,2691 +487,2905 @@ var rest = function rest(collection) {
  * Exports.
  */
 
-module.exports = rest;
+module.exports = drop;
 
-},{}],17:[function(require,module,exports){
-(function (global){
+},{}],6:[function(require,module,exports){
 'use strict';
 
-var _analytics = global.analytics;
-
 /*
  * Module dependencies.
  */
 
-var Alias = require('segmentio-facade').Alias;
-var Emitter = require('component-emitter');
-var Group = require('segmentio-facade').Group;
-var Identify = require('segmentio-facade').Identify;
-var Page = require('segmentio-facade').Page;
-var Track = require('segmentio-facade').Track;
-var after = require('@ndhoule/after');
-var bindAll = require('bind-all');
-var clone = require('@ndhoule/clone');
-var extend = require('extend');
-var cookie = require('./cookie');
-var metrics = require('./metrics');
-var debug = require('debug');
-var defaults = require('@ndhoule/defaults');
-var each = require('@ndhoule/each');
-var foldl = require('@ndhoule/foldl');
-var group = require('./group');
-var is = require('is');
-var isMeta = require('@segment/is-meta');
 var keys = require('@ndhoule/keys');
-var memory = require('./memory');
-var nextTick = require('next-tick');
-var normalize = require('./normalize');
-var on = require('component-event').bind;
-var pageDefaults = require('./pageDefaults');
-var pick = require('@ndhoule/pick');
-var prevent = require('@segment/prevent-default');
-var querystring = require('component-querystring');
-var store = require('./store');
-var user = require('./user');
-var type = require('component-type');
 
-/**
- * Initialize a new `Analytics` instance.
+var objToString = Object.prototype.toString;
+
+/**
+ * Tests if a value is a number.
+ *
+ * @name isNumber
+ * @api private
+ * @param {*} val The value to test.
+ * @return {boolean} Returns `true` if `val` is a number, otherwise `false`.
+ */
+// TODO: Move to library
+var isNumber = function isNumber(val) {
+  var type = typeof val;
+  return type === 'number' || (type === 'object' && objToString.call(val) === 
'[object Number]');
+};
+
+/**
+ * Tests if a value is an array.
+ *
+ * @name isArray
+ * @api private
+ * @param {*} val The value to test.
+ * @return {boolean} Returns `true` if the value is an array, otherwise 
`false`.
+ */
+// TODO: Move to library
+var isArray = typeof Array.isArray === 'function' ? Array.isArray : function 
isArray(val) {
+  return objToString.call(val) === '[object Array]';
+};
+
+/**
+ * Tests if a value is array-like. Array-like means the value is not a 
function and has a numeric
+ * `.length` property.
+ *
+ * @name isArrayLike
+ * @api private
+ * @param {*} val
+ * @return {boolean}
+ */
+// TODO: Move to library
+var isArrayLike = function isArrayLike(val) {
+  return val != null && (isArray(val) || (val !== 'function' && 
isNumber(val.length)));
+};
+
+/**
+ * Internal implementation of `each`. Works on arrays and array-like data 
structures.
+ *
+ * @name arrayEach
+ * @api private
+ * @param {Function(value, key, collection)} iterator The function to invoke 
per iteration.
+ * @param {Array} array The array(-like) structure to iterate over.
+ * @return {undefined}
+ */
+var arrayEach = function arrayEach(iterator, array) {
+  for (var i = 0; i < array.length; i += 1) {
+    // Break iteration early if `iterator` returns `false`
+    if (iterator(array[i], i, array) === false) {
+      break;
+    }
+  }
+};
+
+/**
+ * Internal implementation of `each`. Works on objects.
+ *
+ * @name baseEach
+ * @api private
+ * @param {Function(value, key, collection)} iterator The function to invoke 
per iteration.
+ * @param {Object} object The object to iterate over.
+ * @return {undefined}
+ */
+var baseEach = function baseEach(iterator, object) {
+  var ks = keys(object);
+
+  for (var i = 0; i < ks.length; i += 1) {
+    // Break iteration early if `iterator` returns `false`
+    if (iterator(object[ks[i]], ks[i], object) === false) {
+      break;
+    }
+  }
+};
+
+/**
+ * Iterate over an input collection, invoking an `iterator` function for each 
element in the
+ * collection and passing to it three arguments: `(value, index, collection)`. 
The `iterator`
+ * function can end iteration early by returning `false`.
+ *
+ * @name each
+ * @api public
+ * @param {Function(value, key, collection)} iterator The function to invoke 
per iteration.
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @return {undefined} Because `each` is run only for side effects, always 
returns `undefined`.
+ * @example
+ * var log = console.log.bind(console);
+ *
+ * each(log, ['a', 'b', 'c']);
+ * //-> 'a', 0, ['a', 'b', 'c']
+ * //-> 'b', 1, ['a', 'b', 'c']
+ * //-> 'c', 2, ['a', 'b', 'c']
+ * //=> undefined
+ *
+ * each(log, 'tim');
+ * //-> 't', 2, 'tim'
+ * //-> 'i', 1, 'tim'
+ * //-> 'm', 0, 'tim'
+ * //=> undefined
+ *
+ * // Note: Iteration order not guaranteed across environments
+ * each(log, { name: 'tim', occupation: 'enchanter' });
+ * //-> 'tim', 'name', { name: 'tim', occupation: 'enchanter' }
+ * //-> 'enchanter', 'occupation', { name: 'tim', occupation: 'enchanter' }
+ * //=> undefined
+ */
+var each = function each(iterator, collection) {
+  return (isArrayLike(collection) ? arrayEach : baseEach).call(this, iterator, 
collection);
+};
+
+/*
+ * Exports.
  */
 
-function Analytics() {
-  this._options({});
-  this.Integrations = {};
-  this._integrations = {};
-  this._readied = false;
-  this._timeout = 300;
-  // XXX: BACKWARDS COMPATIBILITY
-  this._user = user;
-  this.log = debug('analytics.js');
-  bindAll(this);
+module.exports = each;
 
-  var self = this;
-  this.on('initialize', function(settings, options) {
-    if (options.initialPageview) self.page();
-    self._parseQuery(window.location.search);
-  });
-}
+},{"@ndhoule/keys":11}],7:[function(require,module,exports){
+'use strict';
 
-/**
- * Mix in event emitter.
+/*
+ * Module dependencies.
  */
 
-Emitter(Analytics.prototype);
+var each = require('@ndhoule/each');
 
 /**
- * Use a `plugin`.
+ * Check if a predicate function returns `true` for all values in a 
`collection`.
+ * Checks owned, enumerable values and exits early when `predicate` returns
+ * `false`.
  *
- * @param {Function} plugin
- * @return {Analytics}
+ * @name every
+ * @param {Function} predicate The function used to test values.
+ * @param {Array|Object|string} collection The collection to search.
+ * @return {boolean} True if all values passes the predicate test, otherwise 
false.
+ * @example
+ * var isEven = function(num) { return num % 2 === 0; };
+ *
+ * every(isEven, []); // => true
+ * every(isEven, [1, 2]); // => false
+ * every(isEven, [2, 4, 6]); // => true
  */
+var every = function every(predicate, collection) {
+  if (typeof predicate !== 'function') {
+    throw new TypeError('`predicate` must be a function but was a ' + typeof 
predicate);
+  }
 
-Analytics.prototype.use = function(plugin) {
-  plugin(this);
-  return this;
+  var result = true;
+
+  each(function(val, key, collection) {
+    result = !!predicate(val, key, collection);
+
+    // Exit early
+    if (!result) {
+      return false;
+    }
+  }, collection);
+
+  return result;
 };
 
-/**
- * Define a new `Integration`.
- *
- * @param {Function} Integration
- * @return {Analytics}
+/*
+ * Exports.
  */
 
-Analytics.prototype.addIntegration = function(Integration) {
-  var name = Integration.prototype.name;
-  if (!name) throw new TypeError('attempted to add an invalid integration');
-  this.Integrations[name] = Integration;
-  return this;
-};
+module.exports = every;
+
+},{"@ndhoule/each":6}],8:[function(require,module,exports){
+'use strict';
+
+var has = Object.prototype.hasOwnProperty;
 
 /**
- * Initialize with the given integration `settings` and `options`.
+ * Copy the properties of one or more `objects` onto a destination object. 
Input objects are iterated over
+ * in left-to-right order, so duplicate properties on later objects will 
overwrite those from
+ * erevious ones. Only enumerable and own properties of the input objects are 
copied onto the
+ * resulting object.
  *
- * Aliased to `init` for convenience.
+ * @name extend
+ * @api public
+ * @category Object
+ * @param {Object} dest The destination object.
+ * @param {...Object} sources The source objects.
+ * @return {Object} `dest`, extended with the properties of all `sources`.
+ * @example
+ * var a = { a: 'a' };
+ * var b = { b: 'b' };
+ * var c = { c: 'c' };
  *
- * @param {Object} [settings={}]
- * @param {Object} [options={}]
- * @return {Analytics}
+ * extend(a, b, c);
+ * //=> { a: 'a', b: 'b', c: 'c' };
  */
+var extend = function extend(dest /*, sources */) {
+  var sources = Array.prototype.slice.call(arguments, 1);
 
-Analytics.prototype.init = Analytics.prototype.initialize = function(
-  settings,
-  options
-) {
-  settings = settings || {};
-  options = options || {};
-
-  this._options(options);
-  this._readied = false;
-
-  // clean unknown integrations from settings
-  var self = this;
-  each(function(opts, name) {
-    var Integration = self.Integrations[name];
-    if (!Integration) delete settings[name];
-  }, settings);
-
-  // add integrations
-  each(function(opts, name) {
-    // Don't load disabled integrations
-    if (options.integrations) {
-      if (
-        options.integrations[name] === false ||
-        (options.integrations.All === false && !options.integrations[name])
-      ) {
-        return;
+  for (var i = 0; i < sources.length; i += 1) {
+    for (var key in sources[i]) {
+      if (has.call(sources[i], key)) {
+        dest[key] = sources[i][key];
       }
     }
-
-    var Integration = self.Integrations[name];
-    var clonedOpts = {};
-    extend(true, clonedOpts, opts); // deep clone opts
-    var integration = new Integration(clonedOpts);
-    self.log('initialize %o - %o', name, opts);
-    self.add(integration);
-  }, settings);
-
-  var integrations = this._integrations;
-
-  // load user now that options are set
-  user.load();
-  group.load();
-
-  // make ready callback
-  var integrationCount = keys(integrations).length;
-  var ready = after(integrationCount, function() {
-    self._readied = true;
-    self.emit('ready');
-  });
-
-  // init if no integrations
-  if (integrationCount <= 0) {
-    ready();
   }
 
-  // initialize integrations, passing ready
-  // create a list of any integrations that did not initialize - this will be 
passed with all events for replay support:
-  this.failedInitializations = [];
-  each(function(integration) {
-    if (
-      options.initialPageview &&
-      integration.options.initialPageview === false
-    ) {
-      integration.page = after(2, integration.page);
-    }
+  return dest;
+};
 
-    integration.analytics = self;
-    integration.once('ready', ready);
-    try {
-      metrics.increment('analytics_js.integration.invoke', {
-        method: 'initialize',
-        integration_name: integration.name
-      });
-      integration.initialize();
-    } catch (e) {
-      var integrationName = integration.name;
-      metrics.increment('analytics_js.integration.invoke.error', {
-        method: 'initialize',
-        integration_name: integration.name
-      });
-      self.failedInitializations.push(integrationName);
-      self.log('Error initializing %s integration: %o', integrationName, e);
-      // Mark integration as ready to prevent blocking of anyone listening to 
analytics.ready()
-      integration.ready();
-    }
-  }, integrations);
+/*
+ * Exports.
+ */
 
-  // backwards compat with angular plugin.
-  // TODO: remove
-  this.initialized = true;
+module.exports = extend;
 
-  this.emit('initialize', settings, options);
-  return this;
-};
+},{}],9:[function(require,module,exports){
+'use strict';
 
-/**
- * Set the user's `id`.
- *
- * @param {Mixed} id
+/*
+ * Module dependencies.
  */
 
-Analytics.prototype.setAnonymousId = function(id) {
-  this.user().anonymousId(id);
-  return this;
-};
+var each = require('@ndhoule/each');
 
 /**
- * Add an integration.
+ * Reduces all the values in a collection down into a single value. Does so by 
iterating through the
+ * collection from left to right, repeatedly calling an `iterator` function 
and passing to it four
+ * arguments: `(accumulator, value, index, collection)`.
  *
- * @param {Integration} integration
+ * Returns the final return value of the `iterator` function.
+ *
+ * @name foldl
+ * @api public
+ * @param {Function} iterator The function to invoke per iteration.
+ * @param {*} accumulator The initial accumulator value, passed to the first 
invocation of `iterator`.
+ * @param {Array|Object} collection The collection to iterate over.
+ * @return {*} The return value of the final call to `iterator`.
+ * @example
+ * foldl(function(total, n) {
+ *   return total + n;
+ * }, 0, [1, 2, 3]);
+ * //=> 6
+ *
+ * var phonebook = { bob: '555-111-2345', tim: '655-222-6789', sheila: 
'655-333-1298' };
+ *
+ * foldl(function(results, phoneNumber) {
+ *  if (phoneNumber[0] === '6') {
+ *    return results.concat(phoneNumber);
+ *  }
+ *  return results;
+ * }, [], phonebook);
+ * // => ['655-222-6789', '655-333-1298']
  */
+var foldl = function foldl(iterator, accumulator, collection) {
+  if (typeof iterator !== 'function') {
+    throw new TypeError('Expected a function but received a ' + typeof 
iterator);
+  }
 
-Analytics.prototype.add = function(integration) {
-  this._integrations[integration.name] = integration;
-  return this;
+  each(function(val, i, collection) {
+    accumulator = iterator(accumulator, val, i, collection);
+  }, collection);
+
+  return accumulator;
 };
 
-/**
- * Identify a user by optional `id` and `traits`.
- *
- * @param {string} [id=user.id()] User ID.
- * @param {Object} [traits=null] User traits.
- * @param {Object} [options=null]
- * @param {Function} [fn]
- * @return {Analytics}
+/*
+ * Exports.
  */
 
-Analytics.prototype.identify = function(id, traits, options, fn) {
-  // Argument reshuffling.
-  /* eslint-disable no-unused-expressions, no-sequences */
-  if (is.fn(options)) (fn = options), (options = null);
-  if (is.fn(traits)) (fn = traits), (options = null), (traits = null);
-  if (is.object(id)) (options = traits), (traits = id), (id = user.id());
-  /* eslint-enable no-unused-expressions, no-sequences */
-
-  // clone traits before we manipulate so we don't do anything uncouth, and 
take
-  // from `user` so that we carryover anonymous traits
-  user.identify(id, traits);
+module.exports = foldl;
 
-  var msg = this.normalize({
-    options: options,
-    traits: user.traits(),
-    userId: user.id()
-  });
+},{"@ndhoule/each":6}],10:[function(require,module,exports){
+'use strict';
 
-  // Add the initialize integrations so the server-side ones can be disabled 
too
-  if (this.options.integrations) {
-    defaults(msg.integrations, this.options.integrations);
-  }
+/*
+ * Module dependencies.
+ */
 
-  this._invoke('identify', new Identify(msg));
+var each = require('@ndhoule/each');
 
-  // emit
-  this.emit('identify', id, traits, options);
-  this._callback(fn);
-  return this;
-};
+var strIndexOf = String.prototype.indexOf;
 
 /**
- * Return the current user.
+ * Object.is/sameValueZero polyfill.
  *
- * @return {Object}
+ * @api private
+ * @param {*} value1
+ * @param {*} value2
+ * @return {boolean}
  */
-
-Analytics.prototype.user = function() {
-  return user;
+// TODO: Move to library
+var sameValueZero = function sameValueZero(value1, value2) {
+  // Normal values and check for 0 / -0
+  if (value1 === value2) {
+    return value1 !== 0 || 1 / value1 === 1 / value2;
+  }
+  // NaN
+  return value1 !== value1 && value2 !== value2;
 };
 
 /**
- * Identify a group by optional `id` and `traits`. Or, if no arguments are
- * supplied, return the current group.
+ * Searches a given `collection` for a value, returning true if the collection
+ * contains the value and false otherwise. Can search strings, arrays, and
+ * objects.
  *
- * @param {string} [id=group.id()] Group ID.
- * @param {Object} [traits=null] Group traits.
- * @param {Object} [options=null]
- * @param {Function} [fn]
- * @return {Analytics|Object}
+ * @name includes
+ * @api public
+ * @param {*} searchElement The element to search for.
+ * @param {Object|Array|string} collection The collection to search.
+ * @return {boolean}
+ * @example
+ * includes(2, [1, 2, 3]);
+ * //=> true
+ *
+ * includes(4, [1, 2, 3]);
+ * //=> false
+ *
+ * includes(2, { a: 1, b: 2, c: 3 });
+ * //=> true
+ *
+ * includes('a', { a: 1, b: 2, c: 3 });
+ * //=> false
+ *
+ * includes('abc', 'xyzabc opq');
+ * //=> true
+ *
+ * includes('nope', 'xyzabc opq');
+ * //=> false
  */
+var includes = function includes(searchElement, collection) {
+  var found = false;
 
-Analytics.prototype.group = function(id, traits, options, fn) {
-  /* eslint-disable no-unused-expressions, no-sequences */
-  if (!arguments.length) return group;
-  if (is.fn(options)) (fn = options), (options = null);
-  if (is.fn(traits)) (fn = traits), (options = null), (traits = null);
-  if (is.object(id)) (options = traits), (traits = id), (id = group.id());
-  /* eslint-enable no-unused-expressions, no-sequences */
-
-  // grab from group again to make sure we're taking from the source
-  group.identify(id, traits);
-
-  var msg = this.normalize({
-    options: options,
-    traits: group.traits(),
-    groupId: group.id()
-  });
-
-  // Add the initialize integrations so the server-side ones can be disabled 
too
-  if (this.options.integrations) {
-    defaults(msg.integrations, this.options.integrations);
+  // Delegate to String.prototype.indexOf when `collection` is a string
+  if (typeof collection === 'string') {
+    return strIndexOf.call(collection, searchElement) !== -1;
   }
 
-  this._invoke('group', new Group(msg));
+  // Iterate through enumerable/own array elements and object properties.
+  each(function(value) {
+    if (sameValueZero(value, searchElement)) {
+      found = true;
+      // Exit iteration early when found
+      return false;
+    }
+  }, collection);
 
-  this.emit('group', id, traits, options);
-  this._callback(fn);
-  return this;
+  return found;
 };
 
-/**
- * Track an `event` that a user has triggered with optional `properties`.
- *
- * @param {string} event
- * @param {Object} [properties=null]
- * @param {Object} [options=null]
- * @param {Function} [fn]
- * @return {Analytics}
+/*
+ * Exports.
  */
 
-Analytics.prototype.track = function(event, properties, options, fn) {
-  // Argument reshuffling.
-  /* eslint-disable no-unused-expressions, no-sequences */
-  if (is.fn(options)) (fn = options), (options = null);
-  if (is.fn(properties))
-    (fn = properties), (options = null), (properties = null);
-  /* eslint-enable no-unused-expressions, no-sequences */
-
-  // figure out if the event is archived.
-  var plan = this.options.plan || {};
-  var events = plan.track || {};
-  var planIntegrationOptions = {};
-
-  // normalize
-  var msg = this.normalize({
-    properties: properties,
-    options: options,
-    event: event
-  });
-
-  // plan.
-  plan = events[event];
-  if (plan) {
-    this.log('plan %o - %o', event, plan);
-    if (plan.enabled === false) {
-      // Disabled events should always be sent to Segment.
-      planIntegrationOptions = { All: false, 'Segment.io': true };
-    } else {
-      planIntegrationOptions = plan.integrations || {};
-    }
-  } else {
-    var defaultPlan = events.__default || { enabled: true };
-    if (!defaultPlan.enabled) {
-      // Disabled events should always be sent to Segment.
-      planIntegrationOptions = { All: false, 'Segment.io': true };
-    }
-  }
+module.exports = includes;
 
-  // Add the initialize integrations so the server-side ones can be disabled 
too
-  defaults(
-    msg.integrations,
-    this._mergeInitializeAndPlanIntegrations(planIntegrationOptions)
-  );
+},{"@ndhoule/each":6}],11:[function(require,module,exports){
+'use strict';
 
-  this._invoke('track', new Track(msg));
+var hop = Object.prototype.hasOwnProperty;
+var strCharAt = String.prototype.charAt;
+var toStr = Object.prototype.toString;
 
-  this.emit('track', event, properties, options);
-  this._callback(fn);
-  return this;
+/**
+ * Returns the character at a given index.
+ *
+ * @param {string} str
+ * @param {number} index
+ * @return {string|undefined}
+ */
+// TODO: Move to a library
+var charAt = function(str, index) {
+  return strCharAt.call(str, index);
 };
 
 /**
- * Helper method to track an outbound link that would normally navigate away
- * from the page before the analytics calls were sent.
- *
- * BACKWARDS COMPATIBILITY: aliased to `trackClick`.
+ * hasOwnProperty, wrapped as a function.
  *
- * @param {Element|Array} links
- * @param {string|Function} event
- * @param {Object|Function} properties (optional)
- * @return {Analytics}
+ * @name has
+ * @api private
+ * @param {*} context
+ * @param {string|number} prop
+ * @return {boolean}
  */
 
-Analytics.prototype.trackClick = Analytics.prototype.trackLink = function(
-  links,
-  event,
-  properties
-) {
-  if (!links) return this;
-  // always arrays, handles jquery
-  if (type(links) === 'element') links = [links];
-
-  var self = this;
-  each(function(el) {
-    if (type(el) !== 'element') {
-      throw new TypeError('Must pass HTMLElement to `analytics.trackLink`.');
-    }
-    on(el, 'click', function(e) {
-      var ev = is.fn(event) ? event(el) : event;
-      var props = is.fn(properties) ? properties(el) : properties;
-      var href =
-        el.getAttribute('href') ||
-        el.getAttributeNS('http://www.w3.org/1999/xlink', 'href') ||
-        el.getAttribute('xlink:href');
-
-      self.track(ev, props);
+// TODO: Move to a library
+var has = function has(context, prop) {
+  return hop.call(context, prop);
+};
 
-      if (href && el.target !== '_blank' && !isMeta(e)) {
-        prevent(e);
-        self._callback(function() {
-          window.location.href = href;
-        });
-      }
-    });
-  }, links);
+/**
+ * Returns true if a value is a string, otherwise false.
+ *
+ * @name isString
+ * @api private
+ * @param {*} val
+ * @return {boolean}
+ */
 
-  return this;
+// TODO: Move to a library
+var isString = function isString(val) {
+  return toStr.call(val) === '[object String]';
 };
 
 /**
- * Helper method to track an outbound form that would normally navigate away
- * from the page before the analytics calls were sent.
- *
- * BACKWARDS COMPATIBILITY: aliased to `trackSubmit`.
+ * Returns true if a value is array-like, otherwise false. Array-like means a
+ * value is not null, undefined, or a function, and has a numeric `length`
+ * property.
  *
- * @param {Element|Array} forms
- * @param {string|Function} event
- * @param {Object|Function} properties (optional)
- * @return {Analytics}
+ * @name isArrayLike
+ * @api private
+ * @param {*} val
+ * @return {boolean}
  */
+// TODO: Move to a library
+var isArrayLike = function isArrayLike(val) {
+  return val != null && (typeof val !== 'function' && typeof val.length === 
'number');
+};
 
-Analytics.prototype.trackSubmit = Analytics.prototype.trackForm = function(
-  forms,
-  event,
-  properties
-) {
-  if (!forms) return this;
-  // always arrays, handles jquery
-  if (type(forms) === 'element') forms = [forms];
-
-  var self = this;
-  each(function(el) {
-    if (type(el) !== 'element')
-      throw new TypeError('Must pass HTMLElement to `analytics.trackForm`.');
-    function handler(e) {
-      prevent(e);
 
-      var ev = is.fn(event) ? event(el) : event;
-      var props = is.fn(properties) ? properties(el) : properties;
-      self.track(ev, props);
+/**
+ * indexKeys
+ *
+ * @name indexKeys
+ * @api private
+ * @param {} target
+ * @param {Function} pred
+ * @return {Array}
+ */
+var indexKeys = function indexKeys(target, pred) {
+  pred = pred || has;
 
-      self._callback(function() {
-        el.submit();
-      });
-    }
+  var results = [];
 
-    // Support the events happening through jQuery or Zepto instead of through
-    // the normal DOM API, because `el.submit` doesn't bubble up events...
-    var $ = window.jQuery || window.Zepto;
-    if ($) {
-      $(el).submit(handler);
-    } else {
-      on(el, 'submit', handler);
+  for (var i = 0, len = target.length; i < len; i += 1) {
+    if (pred(target, i)) {
+      results.push(String(i));
     }
-  }, forms);
+  }
 
-  return this;
+  return results;
 };
 
 /**
- * Trigger a pageview, labeling the current page with an optional `category`,
- * `name` and `properties`.
+ * Returns an array of an object's owned keys.
  *
- * @param {string} [category]
- * @param {string} [name]
- * @param {Object|string} [properties] (or path)
- * @param {Object} [options]
- * @param {Function} [fn]
- * @return {Analytics}
+ * @name objectKeys
+ * @api private
+ * @param {*} target
+ * @param {Function} pred Predicate function used to include/exclude values 
from
+ * the resulting array.
+ * @return {Array}
  */
+var objectKeys = function objectKeys(target, pred) {
+  pred = pred || has;
 
-Analytics.prototype.page = function(category, name, properties, options, fn) {
-  // Argument reshuffling.
-  /* eslint-disable no-unused-expressions, no-sequences */
-  if (is.fn(options)) (fn = options), (options = null);
-  if (is.fn(properties)) (fn = properties), (options = properties = null);
-  if (is.fn(name)) (fn = name), (options = properties = name = null);
-  if (type(category) === 'object')
-    (options = name), (properties = category), (name = category = null);
-  if (type(name) === 'object')
-    (options = properties), (properties = name), (name = null);
-  if (type(category) === 'string' && type(name) !== 'string')
-    (name = category), (category = null);
-  /* eslint-enable no-unused-expressions, no-sequences */
+  var results = [];
 
-  properties = clone(properties) || {};
-  if (name) properties.name = name;
-  if (category) properties.category = category;
+  for (var key in target) {
+    if (pred(target, key)) {
+      results.push(String(key));
+    }
+  }
 
-  // Ensure properties has baseline spec properties.
-  // TODO: Eventually move these entirely to `options.context.page`
-  var defs = pageDefaults();
-  defaults(properties, defs);
+  return results;
+};
 
-  // Mirror user overrides to `options.context.page` (but exclude custom 
properties)
-  // (Any page defaults get applied in `this.normalize` for consistency.)
-  // Weird, yeah--moving special props to `context.page` will fix this in the 
long term.
-  var overrides = pick(keys(defs), properties);
-  if (!is.empty(overrides)) {
-    options = options || {};
-    options.context = options.context || {};
-    options.context.page = overrides;
+/**
+ * Creates an array composed of all keys on the input object. Ignores any 
non-enumerable properties.
+ * More permissive than the native `Object.keys` function (non-objects will 
not throw errors).
+ *
+ * @name keys
+ * @api public
+ * @category Object
+ * @param {Object} source The value to retrieve keys from.
+ * @return {Array} An array containing all the input `source`'s keys.
+ * @example
+ * keys({ likes: 'avocado', hates: 'pineapple' });
+ * //=> ['likes', 'pineapple'];
+ *
+ * // Ignores non-enumerable properties
+ * var hasHiddenKey = { name: 'Tim' };
+ * Object.defineProperty(hasHiddenKey, 'hidden', {
+ *   value: 'i am not enumerable!',
+ *   enumerable: false
+ * })
+ * keys(hasHiddenKey);
+ * //=> ['name'];
+ *
+ * // Works on arrays
+ * keys(['a', 'b', 'c']);
+ * //=> ['0', '1', '2']
+ *
+ * // Skips unpopulated indices in sparse arrays
+ * var arr = [1];
+ * arr[4] = 4;
+ * keys(arr);
+ * //=> ['0', '4']
+ */
+var keys = function keys(source) {
+  if (source == null) {
+    return [];
   }
 
-  var msg = this.normalize({
-    properties: properties,
-    category: category,
-    options: options,
-    name: name
-  });
-
-  // Add the initialize integrations so the server-side ones can be disabled 
too
-  if (this.options.integrations) {
-    defaults(msg.integrations, this.options.integrations);
+  // IE6-8 compatibility (string)
+  if (isString(source)) {
+    return indexKeys(source, charAt);
   }
 
-  this._invoke('page', new Page(msg));
+  // IE6-8 compatibility (arguments)
+  if (isArrayLike(source)) {
+    return indexKeys(source, has);
+  }
 
-  this.emit('page', category, name, properties, options);
-  this._callback(fn);
-  return this;
+  return objectKeys(source);
 };
 
-/**
- * FIXME: BACKWARDS COMPATIBILITY: convert an old `pageview` to a `page` call.
- *
- * @param {string} [url]
- * @return {Analytics}
- * @api private
+/*
+ * Exports.
  */
 
-Analytics.prototype.pageview = function(url) {
-  var properties = {};
-  if (url) properties.path = url;
-  this.page(properties);
-  return this;
-};
+module.exports = keys;
 
-/**
- * Merge two previously unassociated user identities.
- *
- * @param {string} to
- * @param {string} from (optional)
- * @param {Object} options (optional)
- * @param {Function} fn (optional)
- * @return {Analytics}
- */
+},{}],12:[function(require,module,exports){
+'use strict';
 
-Analytics.prototype.alias = function(to, from, options, fn) {
-  // Argument reshuffling.
-  /* eslint-disable no-unused-expressions, no-sequences */
-  if (is.fn(options)) (fn = options), (options = null);
-  if (is.fn(from)) (fn = from), (options = null), (from = null);
-  if (is.object(from)) (options = from), (from = null);
-  /* eslint-enable no-unused-expressions, no-sequences */
+/*
+ * Module dependencies.
+ */
 
-  var msg = this.normalize({
-    options: options,
-    previousId: from,
-    userId: to
-  });
+var each = require('@ndhoule/each');
 
-  // Add the initialize integrations so the server-side ones can be disabled 
too
-  if (this.options.integrations) {
-    defaults(msg.integrations, this.options.integrations);
+/**
+ * Produce a new array by passing each value in the input `collection` through 
a transformative
+ * `iterator` function. The `iterator` function is passed three arguments:
+ * `(value, index, collection)`.
+ *
+ * @name map
+ * @api public
+ * @param {Function} iterator The transformer function to invoke per iteration.
+ * @param {Array} collection The collection to iterate over.
+ * @return {Array} A new array containing the results of each `iterator` 
invocation.
+ * @example
+ * var square = function(x) { return x * x; };
+ *
+ * map(square, [1, 2, 3]);
+ * //=> [1, 4, 9]
+ */
+var map = function map(iterator, collection) {
+  if (typeof iterator !== 'function') {
+    throw new TypeError('Expected a function but received a ' + typeof 
iterator);
   }
 
-  this._invoke('alias', new Alias(msg));
+  var result = [];
+
+  each(function(val, i, collection) {
+    result.push(iterator(val, i, collection));
+  }, collection);
 
-  this.emit('alias', to, from, options);
-  this._callback(fn);
-  return this;
+  return result;
 };
 
-/**
- * Register a `fn` to be fired when all the analytics services are ready.
- *
- * @param {Function} fn
- * @return {Analytics}
+/*
+ * Exports.
  */
 
-Analytics.prototype.ready = function(fn) {
-  if (is.fn(fn)) {
-    if (this._readied) {
-      nextTick(fn);
-    } else {
-      this.once('ready', fn);
-    }
-  }
-  return this;
+module.exports = map;
+
+},{"@ndhoule/each":6}],13:[function(require,module,exports){
+'use strict';
+
+var objToString = Object.prototype.toString;
+
+// TODO: Move to lib
+var existy = function(val) {
+  return val != null;
 };
 
-/**
- * Set the `timeout` (in milliseconds) used for callbacks.
- *
- * @param {Number} timeout
- */
+// TODO: Move to lib
+var isArray = function(val) {
+  return objToString.call(val) === '[object Array]';
+};
 
-Analytics.prototype.timeout = function(timeout) {
-  this._timeout = timeout;
+// TODO: Move to lib
+var isString = function(val) {
+   return typeof val === 'string' || objToString.call(val) === '[object 
String]';
+};
+
+// TODO: Move to lib
+var isObject = function(val) {
+  return val != null && typeof val === 'object';
 };
 
 /**
- * Enable or disable debug.
+ * Returns a copy of the new `object` containing only the specified properties.
  *
- * @param {string|boolean} str
+ * @name pick
+ * @api public
+ * @param {string|string[]} props The property or properties to keep.
+ * @param {Object} object The object to iterate over.
+ * @return {Object} A new object containing only the specified properties from 
`object`.
+ * @example
+ * var person = { name: 'Tim', occupation: 'enchanter', fears: 'rabbits' };
+ *
+ * pick('name', person);
+ * //=> { name: 'Tim' }
+ *
+ * pick(['name', 'fears'], person);
+ * //=> { name: 'Tim', fears: 'rabbits' }
  */
+var pick = function pick(props, object) {
+  if (!existy(object) || !isObject(object)) {
+    return {};
+  }
 
-Analytics.prototype.debug = function(str) {
-  if (!arguments.length || str) {
-    debug.enable('analytics:' + (str || '*'));
-  } else {
-    debug.disable();
+  if (isString(props)) {
+    props = [props];
+  }
+
+  if (!isArray(props)) {
+    props = [];
   }
+
+  var result = {};
+
+  for (var i = 0; i < props.length; i += 1) {
+    if (isString(props[i]) && props[i] in object) {
+      result[props[i]] = object[props[i]];
+    }
+  }
+
+  return result;
 };
 
-/**
- * Apply options.
- *
- * @param {Object} options
- * @return {Analytics}
- * @api private
+/*
+ * Exports.
  */
 
-Analytics.prototype._options = function(options) {
-  options = options || {};
-  this.options = options;
-  cookie.options(options.cookie);
-  metrics.options(options.metrics);
-  store.options(options.localStorage);
-  user.options(options.user);
-  group.options(options.group);
-  return this;
-};
+module.exports = pick;
+
+},{}],14:[function(require,module,exports){
+'use strict';
+
+var max = Math.max;
 
 /**
- * Callback a `fn` after our defined timeout period.
+ * Produce a new array by passing each value in the input `collection` through 
a transformative
+ * `iterator` function. The `iterator` function is passed three arguments:
+ * `(value, index, collection)`.
  *
- * @param {Function} fn
- * @return {Analytics}
- * @api private
+ * @name rest
+ * @api public
+ * @param {Array} collection The collection to iterate over.
+ * @return {Array} A new array containing all but the first element from 
`collection`.
+ * @example
+ * rest([1, 2, 3]); // => [2, 3]
  */
+var rest = function rest(collection) {
+  if (collection == null || !collection.length) {
+    return [];
+  }
 
-Analytics.prototype._callback = function(fn) {
-  if (is.fn(fn)) {
-    this._timeout ? setTimeout(fn, this._timeout) : nextTick(fn);
+  // Preallocating an array *significantly* boosts performance when dealing 
with
+  // `arguments` objects on v8. For a summary, see:
+  // 
https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments
+  var results = new Array(max(collection.length - 2, 0));
+
+  for (var i = 1; i < collection.length; i += 1) {
+    results[i - 1] = collection[i];
   }
-  return this;
+
+  return results;
 };
 
-/**
- * Call `method` with `facade` on all enabled integrations.
- *
- * @param {string} method
- * @param {Facade} facade
- * @return {Analytics}
- * @api private
+/*
+ * Exports.
  */
 
-Analytics.prototype._invoke = function(method, facade) {
-  var self = this;
-  metrics.increment('analytics_js.invoke', {
-    method: method
-  });
-  this.emit('invoke', facade);
+module.exports = rest;
 
-  var failedInitializations = self.failedInitializations || [];
-  each(function(integration, name) {
-    if (!facade.enabled(name)) return;
-    // Check if an integration failed to initialize.
-    // If so, do not process the message as the integration is in an unstable 
state.
-    if (failedInitializations.indexOf(name) >= 0) {
-      self.log(
-        'Skipping invokation of .%s method of %s integration. Integation 
failed to initialize properly.',
-        method,
-        name
-      );
-    } else {
-      try {
-        metrics.increment('analytics_js.integration.invoke', {
-          method: method,
-          integration_name: integration.name
-        });
-        integration.invoke.call(integration, method, facade);
-      } catch (e) {
-        metrics.increment('analytics_js.integration.invoke.error', {
-          method: method,
-          integration_name: integration.name
-        });
-        self.log(
-          'Error invoking .%s method of %s integration: %o',
-          method,
-          name,
-          e
-        );
-      }
-    }
-  }, this._integrations);
+},{}],15:[function(require,module,exports){
+(function (global){
+'use strict';
 
-  return this;
-};
+var _analytics = global.analytics;
+
+/*
+ * Module dependencies.
+ */
+
+var Alias = require('segmentio-facade').Alias;
+var Emitter = require('component-emitter');
+var Group = require('segmentio-facade').Group;
+var Identify = require('segmentio-facade').Identify;
+var Page = require('segmentio-facade').Page;
+var Track = require('segmentio-facade').Track;
+var after = require('@ndhoule/after');
+var bindAll = require('bind-all');
+var clone = require('@ndhoule/clone');
+var extend = require('extend');
+var cookie = require('./cookie');
+var metrics = require('./metrics');
+var debug = require('debug');
+var defaults = require('@ndhoule/defaults');
+var each = require('@ndhoule/each');
+var foldl = require('@ndhoule/foldl');
+var group = require('./group');
+var is = require('is');
+var isMeta = require('@segment/is-meta');
+var keys = require('@ndhoule/keys');
+var memory = require('./memory');
+var nextTick = require('next-tick');
+var normalize = require('./normalize');
+var on = require('component-event').bind;
+var pageDefaults = require('./pageDefaults');
+var pick = require('@ndhoule/pick');
+var prevent = require('@segment/prevent-default');
+var querystring = require('component-querystring');
+var store = require('./store');
+var user = require('./user');
+var type = require('component-type');
 
 /**
- * Push `args`.
- *
- * @param {Array} args
- * @api private
+ * Initialize a new `Analytics` instance.
  */
 
-Analytics.prototype.push = function(args) {
-  var method = args.shift();
-  if (!this[method]) return;
-  this[method].apply(this, args);
-};
+function Analytics() {
+  this._options({});
+  this.Integrations = {};
+  this._integrations = {};
+  this._readied = false;
+  this._timeout = 300;
+  // XXX: BACKWARDS COMPATIBILITY
+  this._user = user;
+  this.log = debug('analytics.js');
+  bindAll(this);
+
+  var self = this;
+  this.on('initialize', function(settings, options) {
+    if (options.initialPageview) self.page();
+    self._parseQuery(window.location.search);
+  });
+}
 
 /**
- * Reset group and user traits and id's.
- *
- * @api public
+ * Mix in event emitter.
  */
 
-Analytics.prototype.reset = function() {
-  this.user().logout();
-  this.group().logout();
-};
+Emitter(Analytics.prototype);
 
 /**
- * Parse the query string for callable methods.
+ * Use a `plugin`.
  *
- * @param {String} query
+ * @param {Function} plugin
  * @return {Analytics}
- * @api private
  */
 
-Analytics.prototype._parseQuery = function(query) {
-  // Parse querystring to an object
-  var q = querystring.parse(query);
-  // Create traits and properties objects, populate from querysting params
-  var traits = pickPrefix('ajs_trait_', q);
-  var props = pickPrefix('ajs_prop_', q);
-  // Trigger based on callable parameters in the URL
-  if (q.ajs_uid) this.identify(q.ajs_uid, traits);
-  if (q.ajs_event) this.track(q.ajs_event, props);
-  if (q.ajs_aid) user.anonymousId(q.ajs_aid);
+Analytics.prototype.use = function(plugin) {
+  plugin(this);
   return this;
-
-  /**
-   * Create a shallow copy of an input object containing only the properties
-   * whose keys are specified by a prefix, stripped of that prefix
-   *
-   * @param {String} prefix
-   * @param {Object} object
-   * @return {Object}
-   * @api private
-   */
-
-  function pickPrefix(prefix, object) {
-    var length = prefix.length;
-    var sub;
-    return foldl(
-      function(acc, val, key) {
-        if (key.substr(0, length) === prefix) {
-          sub = key.substr(length);
-          acc[sub] = val;
-        }
-        return acc;
-      },
-      {},
-      object
-    );
-  }
 };
 
 /**
- * Normalize the given `msg`.
+ * Define a new `Integration`.
  *
- * @param {Object} msg
- * @return {Object}
+ * @param {Function} Integration
+ * @return {Analytics}
  */
 
-Analytics.prototype.normalize = function(msg) {
-  msg = normalize(msg, keys(this._integrations));
-  if (msg.anonymousId) user.anonymousId(msg.anonymousId);
-  msg.anonymousId = user.anonymousId();
-
-  // Ensure all outgoing requests include page data in their contexts.
-  msg.context.page = defaults(msg.context.page || {}, pageDefaults());
-
-  return msg;
+Analytics.prototype.addIntegration = function(Integration) {
+  var name = Integration.prototype.name;
+  if (!name) throw new TypeError('attempted to add an invalid integration');
+  this.Integrations[name] = Integration;
+  return this;
 };
 
 /**
- * Merges the tracking plan and initialization integration options.
+ * Initialize with the given integration `settings` and `options`.
  *
- * @param  {Object} planIntegrations Tracking plan integrations.
- * @return {Object}                  The merged integrations.
+ * Aliased to `init` for convenience.
+ *
+ * @param {Object} [settings={}

<TRUNCATED>

Reply via email to