UNPKG

unomi-analytics

Version:

The Apache Unomi analytics.js integration.

1,987 lines (1,699 loc) 422 kB
/*! * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @license Apache-2.0 */ (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){ '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":2}],2:[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; },{}],3:[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":57}],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; /** * 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":5,"@ndhoule/rest":14}],5:[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; },{}],6:[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":11}],7:[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":6}],8:[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; },{}],9:[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":6}],10:[function(require,module,exports){ 'use strict'; /* * Module dependencies. */ var each = require('@ndhoule/each'); var strIndexOf = String.prototype.indexOf; /** * Object.is/sameValueZero polyfill. * * @api private * @param {*} value1 * @param {*} value2 * @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; }; /** * Searches a given `collection` for a value, returning true if the collection * contains the value and false otherwise. Can search strings, arrays, and * objects. * * @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; // Delegate to String.prototype.indexOf when `collection` is a string if (typeof collection === 'string') { return strIndexOf.call(collection, searchElement) !== -1; } // 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); return found; }; /* * Exports. */ module.exports = includes; },{"@ndhoule/each":6}],11:[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); }; /** * hasOwnProperty, wrapped as a function. * * @name has * @api private * @param {*} context * @param {string|number} prop * @return {boolean} */ // TODO: Move to a library var has = function has(context, prop) { return hop.call(context, prop); }; /** * Returns true if a value is a string, otherwise false. * * @name isString * @api private * @param {*} val * @return {boolean} */ // TODO: Move to a library var isString = function isString(val) { return toStr.call(val) === '[object String]'; }; /** * 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'); }; /** * indexKeys * * @name indexKeys * @api private * @param {} target * @param {Function} pred * @return {Array} */ var indexKeys = function indexKeys(target, pred) { pred = pred || has; var results = []; for (var i = 0, len = target.length; i < len; i += 1) { if (pred(target, i)) { results.push(String(i)); } } return results; }; /** * 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; var results = []; for (var key in target) { if (pred(target, key)) { results.push(String(key)); } } return results; }; /** * 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 []; } // IE6-8 compatibility (string) if (isString(source)) { return indexKeys(source, charAt); } // IE6-8 compatibility (arguments) if (isArrayLike(source)) { return indexKeys(source, has); } return objectKeys(source); }; /* * Exports. */ module.exports = keys; },{}],12:[function(require,module,exports){ 'use strict'; /* * Module dependencies. */ var each = require('@ndhoule/each'); /** * 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); } var result = []; each(function(val, i, collection) { result.push(iterator(val, i, collection)); }, collection); return result; }; /* * Exports. */ 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; }; // TODO: Move to lib var isArray = function(val) { return objToString.call(val) === '[object Array]'; }; // 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'; }; /** * 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' } * * pick(['name', 'fears'], person); * //=> { name: 'Tim', fears: 'rabbits' } */ var pick = function pick(props, object) { if (!existy(object) || !isObject(object)) { return {}; } 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; }; /* * Exports. */ module.exports = pick; },{}],14:[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)`. * * @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 []; } // 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 results; }; /* * Exports. */ module.exports = rest; },{}],15:[function(require,module,exports){ (function (global){ 'use strict'; var _analytics = global.analytics; /* * Module dependencies. */ var Alias = require('segmentio-facade').Alias; var Emitter = require('component-emitter'); var Facade = require('segmentio-facade'); var Group = require('segmentio-facade').Group; var Identify = require('segmentio-facade').Identify; var SourceMiddlewareChain = require('./middleware').SourceMiddlewareChain; var IntegrationMiddlewareChain = require('./middleware') .IntegrationMiddlewareChain; var Page = require('segmentio-facade').Page; var Track = require('segmentio-facade').Track; 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. */ function Analytics() { this._options({}); this.Integrations = {}; this._sourceMiddlewares = new SourceMiddlewareChain(); this._integrationMiddlewares = new IntegrationMiddlewareChain(); 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); }); } /** * Mix in event emitter. */ Emitter(Analytics.prototype); /** * Use a `plugin`. * * @param {Function} plugin * @return {Analytics} */ Analytics.prototype.use = function(plugin) { plugin(this); return this; }; /** * Define a new `Integration`. * * @param {Function} Integration * @return {Analytics} */ 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; }; /** * Define a new `SourceMiddleware` * * @param {Function} Middleware * @return {Analytics} */ Analytics.prototype.addSourceMiddleware = function(middleware) { if (this.initialized) throw new Error( 'attempted to add a source middleware after initialization' ); this._sourceMiddlewares.add(middleware); return this; }; /** * Define a new `IntegrationMiddleware` * * @param {Function} Middleware * @return {Analytics} */ Analytics.prototype.addIntegrationMiddleware = function(middleware) { if (this.initialized) throw new Error( 'attempted to add an integration middleware after initialization' ); this._integrationMiddlewares.add(middleware); return this; }; /** * Initialize with the given integration `settings` and `options`. * * Aliased to `init` for convenience. * * @param {Object} [settings={}] * @param {Object} [options={}] * @return {Analytics} */ 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; } } 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 readyCallCount = 0; var integrationCount = keys(integrations).length; var ready = function() { readyCallCount++; if (readyCallCount >= integrationCount) { 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 = []; var initialPageSkipped = false; each(function(integration) { if ( options.initialPageview && integration.options.initialPageview === false ) { // We've assumed one initial pageview, so make sure we don't count the first page call. var page = integration.page; integration.page = function() { if (initialPageSkipped) { return page.apply(this, arguments); } initialPageSkipped = true; return; }; } 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); // backwards compat with angular plugin and used for init logic checks this.initialized = true; this.emit('initialize', settings, options); return this; }; /** * Set the user's `id`. * * @param {Mixed} id */ Analytics.prototype.setAnonymousId = function(id) { this.user().anonymousId(id); return this; }; /** * Add an integration. * * @param {Integration} integration */ Analytics.prototype.add = function(integration) { this._integrations[integration.name] = integration; return this; }; /** * 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} */ 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); var msg = this.normalize({ options: options, traits: user.traits(), userId: user.id() }); // Add the initialize integrations so the server-side ones can be disabled too if (this.options.integrations) { defaults(msg.integrations, this.options.integrations); } this._invoke('identify', new Identify(msg)); // emit this.emit('identify', id, traits, options); this._callback(fn); return this; }; /** * Return the current user. * * @return {Object} */ Analytics.prototype.user = function() { return user; }; /** * Identify a group by optional `id` and `traits`. Or, if no arguments are * supplied, return the current group. * * @param {string} [id=group.id()] Group ID. * @param {Object} [traits=null] Group traits. * @param {Object} [options=null] * @param {Function} [fn] * @return {Analytics|Object} */ 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); } this._invoke('group', new Group(msg)); this.emit('group', id, traits, options); this._callback(fn); return this; }; /** * 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} */ 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 }; } } // Add the initialize integrations so the server-side ones can be disabled too defaults( msg.integrations, this._mergeInitializeAndPlanIntegrations(planIntegrationOptions) ); this._invoke('track', new Track(msg)); this.emit('track', event, properties, options); this._callback(fn); return this; }; /** * 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`. * * @param {Element|Array} links * @param {string|Function} event * @param {Object|Function} properties (optional) * @return {Analytics} */ 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); if (href && el.target !== '_blank' && !isMeta(e)) { prevent(e); self._callback(function() { window.location.href = href; }); } }); }, links); return this; }; /** * 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`. * * @param {Element|Array} forms * @param {string|Function} event * @param {Object|Function} properties (optional) * @return {Analytics} */ 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); self._callback(function() { el.submit(); }); } // 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); } }, forms); return this; }; /** * Trigger a pageview, labeling the current page with an optional `category`, * `name` and `properties`. * * @param {string} [category] * @param {string} [name] * @param {Object|string} [properties] (or path) * @param {Object} [options] * @param {Function} [fn] * @return {Analytics} */ 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 */ properties = clone(properties) || {}; if (name) properties.name = name; if (category) properties.category = category; // Ensure properties has baseline spec properties. // TODO: Eventually move these entirely to `options.context.page` var defs = pageDefaults(); defaults(properties, defs); // 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; } 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); } this._invoke('page', new Page(msg)); this.emit('page', category, name, properties, options); this._callback(fn); return this; }; /** * FIXME: BACKWARDS COMPATIBILITY: convert an old `pageview` to a `page` call. * * @param {string} [url] * @return {Analytics} * @api private */ Analytics.prototype.pageview = function(url) { var properties = {}; if (url) properties.path = url; this.page(properties); return this; }; /** * Merge two previously unassociated user identities. * * @param {string} to * @param {string} from (optional) * @param {Object} options (optional) * @param {Function} fn (optional) * @return {Analytics} */ 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 */ var msg = this.normalize({ options: options, previousId: from, userId: to }); // Add the initialize integrations so the server-side ones can be disabled too if (this.options.integrations) { defaults(msg.integrations, this.options.integrations); } this._invoke('alias', new Alias(msg)); this.emit('alias', to, from, options); this._callback(fn); return this; }; /** * Register a `fn` to be fired when all the analytics services are ready. * * @param {Function} fn * @return {Analytics} */ Analytics.prototype.ready = function(fn) { if (is.fn(fn)) { if (this._readied) { nextTick(fn); } else { this.once('ready', fn); } } return this; }; /** * Set the `timeout` (in milliseconds) used for callbacks. * * @param {Number} timeout */ Analytics.prototype.timeout = function(timeout) { this._timeout = timeout; }; /** * Enable or disable debug. * * @param {string|boolean} str */ Analytics.prototype.debug = function(str) { if (!arguments.length || str) { debug.enable('analytics:' + (str || '*')); } else { debug.disable(); } }; /** * Apply options. * * @param {Object} options * @return {Analytics} * @api private */ 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; }; /** * Callback a `fn` after our defined timeout period. * * @param {Function} fn * @return {Analytics} * @api private */ Analytics.prototype._callback = function(fn) { if (is.fn(fn)) { this._timeout ? setTimeout(fn, this._timeout) : nextTick(fn); } return this; }; /** * Call `method` with `facade` on all enabled integrations. * * @param {string} method * @param {Facade} facade * @return {Analytics} * @api private */ Analytics.prototype._invoke = function(method, facade) { var self = this; try { this._sourceMiddlewares.applyMiddlewares( extend(true, new Facade({}), facade), this._integrations, function(result) { // A nullified payload should not be sent. if (result === null) { self.log( 'Payload with method "%s" was null and dropped by source a middleware.', method ); return; } // Check if the payload is still a Facade. If not, convert it to one. if (!(result instanceof Facade)) { result = new Facade(result); } self.emit('invoke', result); metrics.increment('analytics_js.invoke', { method: method }); applyIntegrationMiddlewares(result); } ); } catch (e) { metrics.increment('analytics_js.invoke.error', { method: method }); self.log( 'Error invoking .%s method of %s integration: %o', method, name, e ); } return this; function applyIntegrationMiddlewares(facade) { var failedInitializations = self.failedInitializations || []; each(function(integration, name) { var facadeCopy = extend(true, new Facade({}), facade); if (!facadeCopy.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 invocation of .%s method of %s integration. Integration failed to initialize properly.', method, name ); } else { try { // Apply any integration middlewares that exist, then invoke th