mixpanel-browser
Version:
The official Mixpanel JavaScript browser client library
1,454 lines (1,304 loc) • 236 kB
JavaScript
(function () {
'use strict';
var Config = {
DEBUG: false,
LIB_VERSION: '2.45.0'
};
// since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
var window$1;
if (typeof(window) === 'undefined') {
var loc = {
hostname: ''
};
window$1 = {
navigator: { userAgent: '' },
document: {
location: loc,
referrer: ''
},
screen: { width: 0, height: 0 },
location: loc
};
} else {
window$1 = window;
}
/*
* Saved references to long variable names, so that closure compiler can
* minimize file size.
*/
var ArrayProto = Array.prototype;
var FuncProto = Function.prototype;
var ObjProto = Object.prototype;
var slice = ArrayProto.slice;
var toString = ObjProto.toString;
var hasOwnProperty = ObjProto.hasOwnProperty;
var windowConsole = window$1.console;
var navigator = window$1.navigator;
var document$1 = window$1.document;
var windowOpera = window$1.opera;
var screen = window$1.screen;
var userAgent = navigator.userAgent;
var nativeBind = FuncProto.bind;
var nativeForEach = ArrayProto.forEach;
var nativeIndexOf = ArrayProto.indexOf;
var nativeMap = ArrayProto.map;
var nativeIsArray = Array.isArray;
var breaker = {};
var _ = {
trim: function(str) {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill
return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
}
};
// Console override
var console = {
/** @type {function(...*)} */
log: function() {
if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
try {
windowConsole.log.apply(windowConsole, arguments);
} catch (err) {
_.each(arguments, function(arg) {
windowConsole.log(arg);
});
}
}
},
/** @type {function(...*)} */
warn: function() {
if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
var args = ['Mixpanel warning:'].concat(_.toArray(arguments));
try {
windowConsole.warn.apply(windowConsole, args);
} catch (err) {
_.each(args, function(arg) {
windowConsole.warn(arg);
});
}
}
},
/** @type {function(...*)} */
error: function() {
if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
var args = ['Mixpanel error:'].concat(_.toArray(arguments));
try {
windowConsole.error.apply(windowConsole, args);
} catch (err) {
_.each(args, function(arg) {
windowConsole.error(arg);
});
}
}
},
/** @type {function(...*)} */
critical: function() {
if (!_.isUndefined(windowConsole) && windowConsole) {
var args = ['Mixpanel error:'].concat(_.toArray(arguments));
try {
windowConsole.error.apply(windowConsole, args);
} catch (err) {
_.each(args, function(arg) {
windowConsole.error(arg);
});
}
}
}
};
var log_func_with_prefix = function(func, prefix) {
return function() {
arguments[0] = '[' + prefix + '] ' + arguments[0];
return func.apply(console, arguments);
};
};
var console_with_prefix = function(prefix) {
return {
log: log_func_with_prefix(console.log, prefix),
error: log_func_with_prefix(console.error, prefix),
critical: log_func_with_prefix(console.critical, prefix)
};
};
// UNDERSCORE
// Embed part of the Underscore Library
_.bind = function(func, context) {
var args, bound;
if (nativeBind && func.bind === nativeBind) {
return nativeBind.apply(func, slice.call(arguments, 1));
}
if (!_.isFunction(func)) {
throw new TypeError();
}
args = slice.call(arguments, 2);
bound = function() {
if (!(this instanceof bound)) {
return func.apply(context, args.concat(slice.call(arguments)));
}
var ctor = {};
ctor.prototype = func.prototype;
var self = new ctor();
ctor.prototype = null;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) {
return result;
}
return self;
};
return bound;
};
/**
* @param {*=} obj
* @param {function(...*)=} iterator
* @param {Object=} context
*/
_.each = function(obj, iterator, context) {
if (obj === null || obj === undefined) {
return;
}
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) {
return;
}
}
} else {
for (var key in obj) {
if (hasOwnProperty.call(obj, key)) {
if (iterator.call(context, obj[key], key, obj) === breaker) {
return;
}
}
}
}
};
_.extend = function(obj) {
_.each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
if (source[prop] !== void 0) {
obj[prop] = source[prop];
}
}
});
return obj;
};
_.isArray = nativeIsArray || function(obj) {
return toString.call(obj) === '[object Array]';
};
// from a comment on http://dbj.org/dbj/?p=286
// fails on only one very rare and deliberate custom object:
// var bomb = { toString : undefined, valueOf: function(o) { return "function BOMBA!"; }};
_.isFunction = function(f) {
try {
return /^\s*\bfunction\b/.test(f);
} catch (x) {
return false;
}
};
_.isArguments = function(obj) {
return !!(obj && hasOwnProperty.call(obj, 'callee'));
};
_.toArray = function(iterable) {
if (!iterable) {
return [];
}
if (iterable.toArray) {
return iterable.toArray();
}
if (_.isArray(iterable)) {
return slice.call(iterable);
}
if (_.isArguments(iterable)) {
return slice.call(iterable);
}
return _.values(iterable);
};
_.map = function(arr, callback, context) {
if (nativeMap && arr.map === nativeMap) {
return arr.map(callback, context);
} else {
var results = [];
_.each(arr, function(item) {
results.push(callback.call(context, item));
});
return results;
}
};
_.keys = function(obj) {
var results = [];
if (obj === null) {
return results;
}
_.each(obj, function(value, key) {
results[results.length] = key;
});
return results;
};
_.values = function(obj) {
var results = [];
if (obj === null) {
return results;
}
_.each(obj, function(value) {
results[results.length] = value;
});
return results;
};
_.include = function(obj, target) {
var found = false;
if (obj === null) {
return found;
}
if (nativeIndexOf && obj.indexOf === nativeIndexOf) {
return obj.indexOf(target) != -1;
}
_.each(obj, function(value) {
if (found || (found = (value === target))) {
return breaker;
}
});
return found;
};
_.includes = function(str, needle) {
return str.indexOf(needle) !== -1;
};
// Underscore Addons
_.inherit = function(subclass, superclass) {
subclass.prototype = new superclass();
subclass.prototype.constructor = subclass;
subclass.superclass = superclass.prototype;
return subclass;
};
_.isObject = function(obj) {
return (obj === Object(obj) && !_.isArray(obj));
};
_.isEmptyObject = function(obj) {
if (_.isObject(obj)) {
for (var key in obj) {
if (hasOwnProperty.call(obj, key)) {
return false;
}
}
return true;
}
return false;
};
_.isUndefined = function(obj) {
return obj === void 0;
};
_.isString = function(obj) {
return toString.call(obj) == '[object String]';
};
_.isDate = function(obj) {
return toString.call(obj) == '[object Date]';
};
_.isNumber = function(obj) {
return toString.call(obj) == '[object Number]';
};
_.isElement = function(obj) {
return !!(obj && obj.nodeType === 1);
};
_.encodeDates = function(obj) {
_.each(obj, function(v, k) {
if (_.isDate(v)) {
obj[k] = _.formatDate(v);
} else if (_.isObject(v)) {
obj[k] = _.encodeDates(v); // recurse
}
});
return obj;
};
_.timestamp = function() {
Date.now = Date.now || function() {
return +new Date;
};
return Date.now();
};
_.formatDate = function(d) {
// YYYY-MM-DDTHH:MM:SS in UTC
function pad(n) {
return n < 10 ? '0' + n : n;
}
return d.getUTCFullYear() + '-' +
pad(d.getUTCMonth() + 1) + '-' +
pad(d.getUTCDate()) + 'T' +
pad(d.getUTCHours()) + ':' +
pad(d.getUTCMinutes()) + ':' +
pad(d.getUTCSeconds());
};
_.strip_empty_properties = function(p) {
var ret = {};
_.each(p, function(v, k) {
if (_.isString(v) && v.length > 0) {
ret[k] = v;
}
});
return ret;
};
/*
* this function returns a copy of object after truncating it. If
* passed an Array or Object it will iterate through obj and
* truncate all the values recursively.
*/
_.truncate = function(obj, length) {
var ret;
if (typeof(obj) === 'string') {
ret = obj.slice(0, length);
} else if (_.isArray(obj)) {
ret = [];
_.each(obj, function(val) {
ret.push(_.truncate(val, length));
});
} else if (_.isObject(obj)) {
ret = {};
_.each(obj, function(val, key) {
ret[key] = _.truncate(val, length);
});
} else {
ret = obj;
}
return ret;
};
_.JSONEncode = (function() {
return function(mixed_val) {
var value = mixed_val;
var quote = function(string) {
var escapable = /[\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; // eslint-disable-line no-control-regex
var meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"': '\\"',
'\\': '\\\\'
};
escapable.lastIndex = 0;
return escapable.test(string) ?
'"' + string.replace(escapable, function(a) {
var c = meta[a];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' :
'"' + string + '"';
};
var str = function(key, holder) {
var gap = '';
var indent = ' ';
var i = 0; // The loop counter.
var k = ''; // The member key.
var v = ''; // The member value.
var length = 0;
var mind = gap;
var partial = [];
var value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
case 'object':
// If the type is 'object', we might be dealing with an object or an array or
// null.
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' :
gap ? '[\n' + gap +
partial.join(',\n' + gap) + '\n' +
mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// Iterate through all of the keys in the object.
for (k in value) {
if (hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' :
gap ? '{' + partial.join(',') + '' +
mind + '}' : '{' + partial.join(',') + '}';
gap = mind;
return v;
}
};
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {
'': value
});
};
})();
/**
* From https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js
* Slightly modified to throw a real Error rather than a POJO
*/
_.JSONDecode = (function() {
var at, // The index of the current character
ch, // The current character
escapee = {
'"': '"',
'\\': '\\',
'/': '/',
'b': '\b',
'f': '\f',
'n': '\n',
'r': '\r',
't': '\t'
},
text,
error = function(m) {
var e = new SyntaxError(m);
e.at = at;
e.text = text;
throw e;
},
next = function(c) {
// If a c parameter is provided, verify that it matches the current character.
if (c && c !== ch) {
error('Expected \'' + c + '\' instead of \'' + ch + '\'');
}
// Get the next character. When there are no more characters,
// return the empty string.
ch = text.charAt(at);
at += 1;
return ch;
},
number = function() {
// Parse a number value.
var number,
string = '';
if (ch === '-') {
string = '-';
next('-');
}
while (ch >= '0' && ch <= '9') {
string += ch;
next();
}
if (ch === '.') {
string += '.';
while (next() && ch >= '0' && ch <= '9') {
string += ch;
}
}
if (ch === 'e' || ch === 'E') {
string += ch;
next();
if (ch === '-' || ch === '+') {
string += ch;
next();
}
while (ch >= '0' && ch <= '9') {
string += ch;
next();
}
}
number = +string;
if (!isFinite(number)) {
error('Bad number');
} else {
return number;
}
},
string = function() {
// Parse a string value.
var hex,
i,
string = '',
uffff;
// When parsing for string values, we must look for " and \ characters.
if (ch === '"') {
while (next()) {
if (ch === '"') {
next();
return string;
}
if (ch === '\\') {
next();
if (ch === 'u') {
uffff = 0;
for (i = 0; i < 4; i += 1) {
hex = parseInt(next(), 16);
if (!isFinite(hex)) {
break;
}
uffff = uffff * 16 + hex;
}
string += String.fromCharCode(uffff);
} else if (typeof escapee[ch] === 'string') {
string += escapee[ch];
} else {
break;
}
} else {
string += ch;
}
}
}
error('Bad string');
},
white = function() {
// Skip whitespace.
while (ch && ch <= ' ') {
next();
}
},
word = function() {
// true, false, or null.
switch (ch) {
case 't':
next('t');
next('r');
next('u');
next('e');
return true;
case 'f':
next('f');
next('a');
next('l');
next('s');
next('e');
return false;
case 'n':
next('n');
next('u');
next('l');
next('l');
return null;
}
error('Unexpected "' + ch + '"');
},
value, // Placeholder for the value function.
array = function() {
// Parse an array value.
var array = [];
if (ch === '[') {
next('[');
white();
if (ch === ']') {
next(']');
return array; // empty array
}
while (ch) {
array.push(value());
white();
if (ch === ']') {
next(']');
return array;
}
next(',');
white();
}
}
error('Bad array');
},
object = function() {
// Parse an object value.
var key,
object = {};
if (ch === '{') {
next('{');
white();
if (ch === '}') {
next('}');
return object; // empty object
}
while (ch) {
key = string();
white();
next(':');
if (Object.hasOwnProperty.call(object, key)) {
error('Duplicate key "' + key + '"');
}
object[key] = value();
white();
if (ch === '}') {
next('}');
return object;
}
next(',');
white();
}
}
error('Bad object');
};
value = function() {
// Parse a JSON value. It could be an object, an array, a string,
// a number, or a word.
white();
switch (ch) {
case '{':
return object();
case '[':
return array();
case '"':
return string();
case '-':
return number();
default:
return ch >= '0' && ch <= '9' ? number() : word();
}
};
// Return the json_parse function. It will have access to all of the
// above functions and variables.
return function(source) {
var result;
text = source;
at = 0;
ch = ' ';
result = value();
white();
if (ch) {
error('Syntax error');
}
return result;
};
})();
_.base64Encode = function(data) {
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
ac = 0,
enc = '',
tmp_arr = [];
if (!data) {
return data;
}
data = _.utf8Encode(data);
do { // pack three octets into four hexets
o1 = data.charCodeAt(i++);
o2 = data.charCodeAt(i++);
o3 = data.charCodeAt(i++);
bits = o1 << 16 | o2 << 8 | o3;
h1 = bits >> 18 & 0x3f;
h2 = bits >> 12 & 0x3f;
h3 = bits >> 6 & 0x3f;
h4 = bits & 0x3f;
// use hexets to index into b64, and append result to encoded string
tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
} while (i < data.length);
enc = tmp_arr.join('');
switch (data.length % 3) {
case 1:
enc = enc.slice(0, -2) + '==';
break;
case 2:
enc = enc.slice(0, -1) + '=';
break;
}
return enc;
};
_.utf8Encode = function(string) {
string = (string + '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
var utftext = '',
start,
end;
var stringl = 0,
n;
start = end = 0;
stringl = string.length;
for (n = 0; n < stringl; n++) {
var c1 = string.charCodeAt(n);
var enc = null;
if (c1 < 128) {
end++;
} else if ((c1 > 127) && (c1 < 2048)) {
enc = String.fromCharCode((c1 >> 6) | 192, (c1 & 63) | 128);
} else {
enc = String.fromCharCode((c1 >> 12) | 224, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128);
}
if (enc !== null) {
if (end > start) {
utftext += string.substring(start, end);
}
utftext += enc;
start = end = n + 1;
}
}
if (end > start) {
utftext += string.substring(start, string.length);
}
return utftext;
};
_.UUID = (function() {
// Time/ticks information
// 1*new Date() is a cross browser version of Date.now()
var T = function() {
var d = 1 * new Date(),
i = 0;
// this while loop figures how many browser ticks go by
// before 1*new Date() returns a new number, ie the amount
// of ticks that go by per millisecond
while (d == 1 * new Date()) {
i++;
}
return d.toString(16) + i.toString(16);
};
// Math.Random entropy
var R = function() {
return Math.random().toString(16).replace('.', '');
};
// User agent entropy
// This function takes the user agent string, and then xors
// together each sequence of 8 bytes. This produces a final
// sequence of 8 bytes which it returns as hex.
var UA = function() {
var ua = userAgent,
i, ch, buffer = [],
ret = 0;
function xor(result, byte_array) {
var j, tmp = 0;
for (j = 0; j < byte_array.length; j++) {
tmp |= (buffer[j] << j * 8);
}
return result ^ tmp;
}
for (i = 0; i < ua.length; i++) {
ch = ua.charCodeAt(i);
buffer.unshift(ch & 0xFF);
if (buffer.length >= 4) {
ret = xor(ret, buffer);
buffer = [];
}
}
if (buffer.length > 0) {
ret = xor(ret, buffer);
}
return ret.toString(16);
};
return function() {
var se = (screen.height * screen.width).toString(16);
return (T() + '-' + R() + '-' + UA() + '-' + se + '-' + T());
};
})();
// _.isBlockedUA()
// This is to block various web spiders from executing our JS and
// sending false tracking data
var BLOCKED_UA_STRS = [
'ahrefsbot',
'baiduspider',
'bingbot',
'bingpreview',
'facebookexternal',
'petalbot',
'pinterest',
'screaming frog',
'yahoo! slurp',
'yandexbot',
// a whole bunch of goog-specific crawlers
// https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers
'adsbot-google',
'apis-google',
'duplexweb-google',
'feedfetcher-google',
'google favicon',
'google web preview',
'google-read-aloud',
'googlebot',
'googleweblight',
'mediapartners-google',
'storebot-google'
];
_.isBlockedUA = function(ua) {
var i;
ua = ua.toLowerCase();
for (i = 0; i < BLOCKED_UA_STRS.length; i++) {
if (ua.indexOf(BLOCKED_UA_STRS[i]) !== -1) {
return true;
}
}
return false;
};
/**
* @param {Object=} formdata
* @param {string=} arg_separator
*/
_.HTTPBuildQuery = function(formdata, arg_separator) {
var use_val, use_key, tmp_arr = [];
if (_.isUndefined(arg_separator)) {
arg_separator = '&';
}
_.each(formdata, function(val, key) {
use_val = encodeURIComponent(val.toString());
use_key = encodeURIComponent(key);
tmp_arr[tmp_arr.length] = use_key + '=' + use_val;
});
return tmp_arr.join(arg_separator);
};
_.getQueryParam = function(url, param) {
// Expects a raw URL
param = param.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
var regexS = '[\\?&]' + param + '=([^&#]*)',
regex = new RegExp(regexS),
results = regex.exec(url);
if (results === null || (results && typeof(results[1]) !== 'string' && results[1].length)) {
return '';
} else {
var result = results[1];
try {
result = decodeURIComponent(result);
} catch(err) {
console.error('Skipping decoding for malformed query param: ' + result);
}
return result.replace(/\+/g, ' ');
}
};
// _.cookie
// Methods partially borrowed from quirksmode.org/js/cookies.html
_.cookie = {
get: function(name) {
var nameEQ = name + '=';
var ca = document$1.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1, c.length);
}
if (c.indexOf(nameEQ) === 0) {
return decodeURIComponent(c.substring(nameEQ.length, c.length));
}
}
return null;
},
parse: function(name) {
var cookie;
try {
cookie = _.JSONDecode(_.cookie.get(name)) || {};
} catch (err) {
// noop
}
return cookie;
},
set_seconds: function(name, value, seconds, is_cross_subdomain, is_secure, is_cross_site, domain_override) {
var cdomain = '',
expires = '',
secure = '';
if (domain_override) {
cdomain = '; domain=' + domain_override;
} else if (is_cross_subdomain) {
var domain = extract_domain(document$1.location.hostname);
cdomain = domain ? '; domain=.' + domain : '';
}
if (seconds) {
var date = new Date();
date.setTime(date.getTime() + (seconds * 1000));
expires = '; expires=' + date.toGMTString();
}
if (is_cross_site) {
is_secure = true;
secure = '; SameSite=None';
}
if (is_secure) {
secure += '; secure';
}
document$1.cookie = name + '=' + encodeURIComponent(value) + expires + '; path=/' + cdomain + secure;
},
set: function(name, value, days, is_cross_subdomain, is_secure, is_cross_site, domain_override) {
var cdomain = '', expires = '', secure = '';
if (domain_override) {
cdomain = '; domain=' + domain_override;
} else if (is_cross_subdomain) {
var domain = extract_domain(document$1.location.hostname);
cdomain = domain ? '; domain=.' + domain : '';
}
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = '; expires=' + date.toGMTString();
}
if (is_cross_site) {
is_secure = true;
secure = '; SameSite=None';
}
if (is_secure) {
secure += '; secure';
}
var new_cookie_val = name + '=' + encodeURIComponent(value) + expires + '; path=/' + cdomain + secure;
document$1.cookie = new_cookie_val;
return new_cookie_val;
},
remove: function(name, is_cross_subdomain, domain_override) {
_.cookie.set(name, '', -1, is_cross_subdomain, false, false, domain_override);
}
};
var _localStorageSupported = null;
var localStorageSupported = function(storage, forceCheck) {
if (_localStorageSupported !== null && !forceCheck) {
return _localStorageSupported;
}
var supported = true;
try {
storage = storage || window.localStorage;
var key = '__mplss_' + cheap_guid(8),
val = 'xyz';
storage.setItem(key, val);
if (storage.getItem(key) !== val) {
supported = false;
}
storage.removeItem(key);
} catch (err) {
supported = false;
}
_localStorageSupported = supported;
return supported;
};
// _.localStorage
_.localStorage = {
is_supported: function(force_check) {
var supported = localStorageSupported(null, force_check);
if (!supported) {
console.error('localStorage unsupported; falling back to cookie store');
}
return supported;
},
error: function(msg) {
console.error('localStorage error: ' + msg);
},
get: function(name) {
try {
return window.localStorage.getItem(name);
} catch (err) {
_.localStorage.error(err);
}
return null;
},
parse: function(name) {
try {
return _.JSONDecode(_.localStorage.get(name)) || {};
} catch (err) {
// noop
}
return null;
},
set: function(name, value) {
try {
window.localStorage.setItem(name, value);
} catch (err) {
_.localStorage.error(err);
}
},
remove: function(name) {
try {
window.localStorage.removeItem(name);
} catch (err) {
_.localStorage.error(err);
}
}
};
_.register_event = (function() {
// written by Dean Edwards, 2005
// with input from Tino Zijdel - crisp@xs4all.nl
// with input from Carl Sverre - mail@carlsverre.com
// with input from Mixpanel
// http://dean.edwards.name/weblog/2005/10/add-event/
// https://gist.github.com/1930440
/**
* @param {Object} element
* @param {string} type
* @param {function(...*)} handler
* @param {boolean=} oldSchool
* @param {boolean=} useCapture
*/
var register_event = function(element, type, handler, oldSchool, useCapture) {
if (!element) {
console.error('No valid element provided to register_event');
return;
}
if (element.addEventListener && !oldSchool) {
element.addEventListener(type, handler, !!useCapture);
} else {
var ontype = 'on' + type;
var old_handler = element[ontype]; // can be undefined
element[ontype] = makeHandler(element, handler, old_handler);
}
};
function makeHandler(element, new_handler, old_handlers) {
var handler = function(event) {
event = event || fixEvent(window.event);
// this basically happens in firefox whenever another script
// overwrites the onload callback and doesn't pass the event
// object to previously defined callbacks. All the browsers
// that don't define window.event implement addEventListener
// so the dom_loaded handler will still be fired as usual.
if (!event) {
return undefined;
}
var ret = true;
var old_result, new_result;
if (_.isFunction(old_handlers)) {
old_result = old_handlers(event);
}
new_result = new_handler.call(element, event);
if ((false === old_result) || (false === new_result)) {
ret = false;
}
return ret;
};
return handler;
}
function fixEvent(event) {
if (event) {
event.preventDefault = fixEvent.preventDefault;
event.stopPropagation = fixEvent.stopPropagation;
}
return event;
}
fixEvent.preventDefault = function() {
this.returnValue = false;
};
fixEvent.stopPropagation = function() {
this.cancelBubble = true;
};
return register_event;
})();
var TOKEN_MATCH_REGEX = new RegExp('^(\\w*)\\[(\\w+)([=~\\|\\^\\$\\*]?)=?"?([^\\]"]*)"?\\]$');
_.dom_query = (function() {
/* document.getElementsBySelector(selector)
- returns an array of element objects from the current document
matching the CSS selector. Selectors can contain element names,
class names and ids and can be nested. For example:
elements = document.getElementsBySelector('div#main p a.external')
Will return an array of all 'a' elements with 'external' in their
class attribute that are contained inside 'p' elements that are
contained inside the 'div' element which has id="main"
New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
See http://www.w3.org/TR/css3-selectors/#attribute-selectors
Version 0.4 - Simon Willison, March 25th 2003
-- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
-- Opera 7 fails
Version 0.5 - Carl Sverre, Jan 7th 2013
-- Now uses jQuery-esque `hasClass` for testing class name
equality. This fixes a bug related to '-' characters being
considered not part of a 'word' in regex.
*/
function getAllChildren(e) {
// Returns all children of element. Workaround required for IE5/Windows. Ugh.
return e.all ? e.all : e.getElementsByTagName('*');
}
var bad_whitespace = /[\t\r\n]/g;
function hasClass(elem, selector) {
var className = ' ' + selector + ' ';
return ((' ' + elem.className + ' ').replace(bad_whitespace, ' ').indexOf(className) >= 0);
}
function getElementsBySelector(selector) {
// Attempt to fail gracefully in lesser browsers
if (!document$1.getElementsByTagName) {
return [];
}
// Split selector in to tokens
var tokens = selector.split(' ');
var token, bits, tagName, found, foundCount, i, j, k, elements, currentContextIndex;
var currentContext = [document$1];
for (i = 0; i < tokens.length; i++) {
token = tokens[i].replace(/^\s+/, '').replace(/\s+$/, '');
if (token.indexOf('#') > -1) {
// Token is an ID selector
bits = token.split('#');
tagName = bits[0];
var id = bits[1];
var element = document$1.getElementById(id);
if (!element || (tagName && element.nodeName.toLowerCase() != tagName)) {
// element not found or tag with that ID not found, return false
return [];
}
// Set currentContext to contain just this element
currentContext = [element];
continue; // Skip to next token
}
if (token.indexOf('.') > -1) {
// Token contains a class selector
bits = token.split('.');
tagName = bits[0];
var className = bits[1];
if (!tagName) {
tagName = '*';
}
// Get elements matching tag, filter them for class selector
found = [];
foundCount = 0;
for (j = 0; j < currentContext.length; j++) {
if (tagName == '*') {
elements = getAllChildren(currentContext[j]);
} else {
elements = currentContext[j].getElementsByTagName(tagName);
}
for (k = 0; k < elements.length; k++) {
found[foundCount++] = elements[k];
}
}
currentContext = [];
currentContextIndex = 0;
for (j = 0; j < found.length; j++) {
if (found[j].className &&
_.isString(found[j].className) && // some SVG elements have classNames which are not strings
hasClass(found[j], className)
) {
currentContext[currentContextIndex++] = found[j];
}
}
continue; // Skip to next token
}
// Code to deal with attribute selectors
var token_match = token.match(TOKEN_MATCH_REGEX);
if (token_match) {
tagName = token_match[1];
var attrName = token_match[2];
var attrOperator = token_match[3];
var attrValue = token_match[4];
if (!tagName) {
tagName = '*';
}
// Grab all of the tagName elements within current context
found = [];
foundCount = 0;
for (j = 0; j < currentContext.length; j++) {
if (tagName == '*') {
elements = getAllChildren(currentContext[j]);
} else {
elements = currentContext[j].getElementsByTagName(tagName);
}
for (k = 0; k < elements.length; k++) {
found[foundCount++] = elements[k];
}
}
currentContext = [];
currentContextIndex = 0;
var checkFunction; // This function will be used to filter the elements
switch (attrOperator) {
case '=': // Equality
checkFunction = function(e) {
return (e.getAttribute(attrName) == attrValue);
};
break;
case '~': // Match one of space seperated words
checkFunction = function(e) {
return (e.getAttribute(attrName).match(new RegExp('\\b' + attrValue + '\\b')));
};
break;
case '|': // Match start with value followed by optional hyphen
checkFunction = function(e) {
return (e.getAttribute(attrName).match(new RegExp('^' + attrValue + '-?')));
};
break;
case '^': // Match starts with value
checkFunction = function(e) {
return (e.getAttribute(attrName).indexOf(attrValue) === 0);
};
break;
case '$': // Match ends with value - fails with "Warning" in Opera 7
checkFunction = function(e) {
return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length);
};
break;
case '*': // Match ends with value
checkFunction = function(e) {
return (e.getAttribute(attrName).indexOf(attrValue) > -1);
};
break;
default:
// Just test for existence of attribute
checkFunction = function(e) {
return e.getAttribute(attrName);
};
}
currentContext = [];
currentContextIndex = 0;
for (j = 0; j < found.length; j++) {
if (checkFunction(found[j])) {
currentContext[currentContextIndex++] = found[j];
}
}
// alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
continue; // Skip to next token
}
// If we get here, token is JUST an element (not a class or ID selector)
tagName = token;
found = [];
foundCount = 0;
for (j = 0; j < currentContext.length; j++) {
elements = currentContext[j].getElementsByTagName(tagName);
for (k = 0; k < elements.length; k++) {
found[foundCount++] = elements[k];
}
}
currentContext = found;
}
return currentContext;
}
return function(query) {
if (_.isElement(query)) {
return [query];
} else if (_.isObject(query) && !_.isUndefined(query.length)) {
return query;
} else {
return getElementsBySelector.call(this, query);
}
};
})();
_.info = {
campaignParams: function() {
var campaign_keywords = 'utm_source utm_medium utm_campaign utm_content utm_term'.split(' '),
kw = '',
params = {};
_.each(campaign_keywords, function(kwkey) {
kw = _.getQueryParam(document$1.URL, kwkey);
if (kw.length) {
params[kwkey] = kw;
}
});
return params;
},
searchEngine: function(referrer) {
if (referrer.search('https?://(.*)google.([^/?]*)') === 0) {
return 'google';
} else if (referrer.search('https?://(.*)bing.com') === 0) {
return 'bing';
} else if (referrer.search('https?://(.*)yahoo.com') === 0) {
return 'yahoo';
} else if (referrer.search('https?://(.*)duckduckgo.com') === 0) {
return 'duckduckgo';
} else {
return null;
}
},
searchInfo: function(referrer) {
var search = _.info.searchEngine(referrer),
param = (search != 'yahoo') ? 'q' : 'p',
ret = {};
if (search !== null) {
ret['$search_engine'] = search;
var keyword = _.getQueryParam(referrer, param);