unomi-analytics
Version:
The Apache Unomi analytics.js integration.
1,987 lines (1,699 loc) • 422 kB
JavaScript
/*!
* 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