2018-07-10 16:39:30 +09:00
|
|
|
(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.bitanalytics = 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';
|
|
|
|
|
|
|
|
|
|
var Config = {
|
|
|
|
|
DEBUG: false,
|
|
|
|
|
LIB_VERSION: '2.22.4'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 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$1 = window$1.navigator;
|
|
|
|
|
var document$1 = window$1.document;
|
|
|
|
|
var windowOpera = window$1.opera;
|
|
|
|
|
var screen = window$1.screen;
|
|
|
|
|
var userAgent = navigator$1.userAgent;
|
|
|
|
|
var nativeBind = FuncProto.bind;
|
|
|
|
|
var nativeForEach = ArrayProto.forEach;
|
|
|
|
|
var nativeIndexOf = ArrayProto.indexOf;
|
|
|
|
|
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$1 = {
|
|
|
|
|
/** @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(...[*])} */
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_.bind_instance_methods = function(obj) {
|
|
|
|
|
for (var func in obj) {
|
|
|
|
|
if (typeof(obj[func]) === 'function') {
|
|
|
|
|
obj[func] = _.bind(obj[func], obj);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_.escapeHTML = function(s) {
|
|
|
|
|
var escaped = s;
|
|
|
|
|
if (escaped && _.isString(escaped)) {
|
|
|
|
|
escaped = escaped
|
|
|
|
|
.replace(/&/g, '&')
|
|
|
|
|
.replace(/</g, '<')
|
|
|
|
|
.replace(/>/g, '>')
|
|
|
|
|
.replace(/"/g, '"')
|
|
|
|
|
.replace(/'/g, ''');
|
|
|
|
|
}
|
|
|
|
|
return escaped;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_.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);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_.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;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_.identity = function(value) {
|
|
|
|
|
return value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_.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());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_.safewrap = function(f) {
|
|
|
|
|
return function() {
|
|
|
|
|
try {
|
|
|
|
|
return f.apply(this, arguments);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console$1.critical('Implementation error. Please turn on debug and contact support@mixpanel.com.');
|
|
|
|
|
if (Config.DEBUG){
|
|
|
|
|
console$1.critical(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_.safewrap_class = function(klass, functions) {
|
|
|
|
|
for (var i = 0; i < functions.length; i++) {
|
|
|
|
|
klass.prototype[functions[i]] = _.safewrap(klass.prototype[functions[i]]);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_.safewrap_instance_methods = function(obj) {
|
|
|
|
|
for (var func in obj) {
|
|
|
|
|
if (typeof(obj[func]) === 'function') {
|
|
|
|
|
obj[func] = _.safewrap(obj[func]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_.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
|
|
|
|
|
_.isBlockedUA = function(ua) {
|
|
|
|
|
if (/(google web preview|baiduspider|yandexbot|bingbot|googlebot|yahoo! slurp)/i.test(ua)) {
|
|
|
|
|
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 {
|
|
|
|
|
return decodeURIComponent(results[1]).replace(/\+/g, ' ');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_.getHashParam = function(hash, param) {
|
|
|
|
|
var matches = hash.match(new RegExp(param + '=([^&]*)'));
|
|
|
|
|
return matches ? matches[1] : null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// _.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, cross_subdomain, is_secure) {
|
|
|
|
|
var cdomain = '',
|
|
|
|
|
expires = '',
|
|
|
|
|
secure = '';
|
|
|
|
|
|
|
|
|
|
if (cross_subdomain) {
|
|
|
|
|
var matches = document$1.location.hostname.match(/[a-z0-9][a-z0-9\-]+\.[a-z\.]{2,6}$/i),
|
|
|
|
|
domain = matches ? matches[0] : '';
|
|
|
|
|
|
|
|
|
|
cdomain = ((domain) ? '; domain=.' + domain : '');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (seconds) {
|
|
|
|
|
var date = new Date();
|
|
|
|
|
date.setTime(date.getTime() + (seconds * 1000));
|
|
|
|
|
expires = '; expires=' + date.toGMTString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_secure) {
|
|
|
|
|
secure = '; secure';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document$1.cookie = name + '=' + encodeURIComponent(value) + expires + '; path=/' + cdomain + secure;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
set: function(name, value, days, cross_subdomain, is_secure) {
|
|
|
|
|
var cdomain = '', expires = '', secure = '';
|
|
|
|
|
|
|
|
|
|
if (cross_subdomain) {
|
|
|
|
|
var matches = document$1.location.hostname.match(/[a-z0-9][a-z0-9\-]+\.[a-z\.]{2,6}$/i),
|
|
|
|
|
domain = matches ? matches[0] : '';
|
|
|
|
|
|
|
|
|
|
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_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, cross_subdomain) {
|
|
|
|
|
_.cookie.set(name, '', -1, cross_subdomain);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// _.localStorage
|
|
|
|
|
var _localStorage_supported = null;
|
|
|
|
|
_.localStorage = {
|
|
|
|
|
is_supported: function() {
|
|
|
|
|
if (_localStorage_supported !== null) {
|
|
|
|
|
return _localStorage_supported;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var supported = true;
|
|
|
|
|
try {
|
|
|
|
|
var key = '__mplssupport__',
|
|
|
|
|
val = 'xyz';
|
|
|
|
|
_.localStorage.set(key, val);
|
|
|
|
|
if (_.localStorage.get(key) !== val) {
|
|
|
|
|
supported = false;
|
|
|
|
|
}
|
|
|
|
|
_.localStorage.remove(key);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
supported = false;
|
|
|
|
|
}
|
|
|
|
|
if (!supported) {
|
|
|
|
|
console$1.error('localStorage unsupported; falling back to cookie store');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_localStorage_supported = supported;
|
|
|
|
|
return supported;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
error: function(msg) {
|
|
|
|
|
console$1.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$1.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;
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
_.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(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/);
|
|
|
|
|
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);
|
|
|
|
|
if (keyword.length) {
|
|
|
|
|
ret['mp_keyword'] = keyword;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This function detects which browser is running this script.
|
|
|
|
|
* The order of the checks are important since many user agents
|
|
|
|
|
* include key words used in later checks.
|
|
|
|
|
*/
|
|
|
|
|
browser: function(user_agent, vendor, opera) {
|
|
|
|
|
vendor = vendor || ''; // vendor is undefined for at least IE9
|
|
|
|
|
if (opera || _.includes(user_agent, ' OPR/')) {
|
|
|
|
|
if (_.includes(user_agent, 'Mini')) {
|
|
|
|
|
return 'Opera Mini';
|
|
|
|
|
}
|
|
|
|
|
return 'Opera';
|
|
|
|
|
} else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) {
|
|
|
|
|
return 'BlackBerry';
|
|
|
|
|
} else if (_.includes(user_agent, 'IEMobile') || _.includes(user_agent, 'WPDesktop')) {
|
|
|
|
|
return 'Internet Explorer Mobile';
|
|
|
|
|
} else if (_.includes(user_agent, 'Edge')) {
|
|
|
|
|
return 'Microsoft Edge';
|
|
|
|
|
} else if (_.includes(user_agent, 'FBIOS')) {
|
|
|
|
|
return 'Facebook Mobile';
|
|
|
|
|
} else if (_.includes(user_agent, 'Chrome')) {
|
|
|
|
|
return 'Chrome';
|
|
|
|
|
} else if (_.includes(user_agent, 'CriOS')) {
|
|
|
|
|
return 'Chrome iOS';
|
|
|
|
|
} else if (_.includes(user_agent, 'UCWEB') || _.includes(user_agent, 'UCBrowser')) {
|
|
|
|
|
return 'UC Browser';
|
|
|
|
|
} else if (_.includes(user_agent, 'FxiOS')) {
|
|
|
|
|
return 'Firefox iOS';
|
|
|
|
|
} else if (_.includes(vendor, 'Apple')) {
|
|
|
|
|
if (_.includes(user_agent, 'Mobile')) {
|
|
|
|
|
return 'Mobile Safari';
|
|
|
|
|
}
|
|
|
|
|
return 'Safari';
|
|
|
|
|
} else if (_.includes(user_agent, 'Android')) {
|
|
|
|
|
return 'Android Mobile';
|
|
|
|
|
} else if (_.includes(user_agent, 'Konqueror')) {
|
|
|
|
|
return 'Konqueror';
|
|
|
|
|
} else if (_.includes(user_agent, 'Firefox')) {
|
|
|
|
|
return 'Firefox';
|
|
|
|
|
} else if (_.includes(user_agent, 'MSIE') || _.includes(user_agent, 'Trident/')) {
|
|
|
|
|
return 'Internet Explorer';
|
|
|
|
|
} else if (_.includes(user_agent, 'Gecko')) {
|
|
|
|
|
return 'Mozilla';
|
|
|
|
|
} else {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This function detects which browser version is running this script,
|
|
|
|
|
* parsing major and minor version (e.g., 42.1). User agent strings from:
|
|
|
|
|
* http://www.useragentstring.com/pages/useragentstring.php
|
|
|
|
|
*/
|
|
|
|
|
browserVersion: function(userAgent, vendor, opera) {
|
|
|
|
|
var browser = _.info.browser(userAgent, vendor, opera);
|
|
|
|
|
var versionRegexs = {
|
|
|
|
|
'Internet Explorer Mobile': /rv:(\d+(\.\d+)?)/,
|
|
|
|
|
'Microsoft Edge': /Edge\/(\d+(\.\d+)?)/,
|
|
|
|
|
'Chrome': /Chrome\/(\d+(\.\d+)?)/,
|
|
|
|
|
'Chrome iOS': /CriOS\/(\d+(\.\d+)?)/,
|
|
|
|
|
'UC Browser' : /(UCBrowser|UCWEB)\/(\d+(\.\d+)?)/,
|
|
|
|
|
'Safari': /Version\/(\d+(\.\d+)?)/,
|
|
|
|
|
'Mobile Safari': /Version\/(\d+(\.\d+)?)/,
|
|
|
|
|
'Opera': /(Opera|OPR)\/(\d+(\.\d+)?)/,
|
|
|
|
|
'Firefox': /Firefox\/(\d+(\.\d+)?)/,
|
|
|
|
|
'Firefox iOS': /FxiOS\/(\d+(\.\d+)?)/,
|
|
|
|
|
'Konqueror': /Konqueror:(\d+(\.\d+)?)/,
|
|
|
|
|
'BlackBerry': /BlackBerry (\d+(\.\d+)?)/,
|
|
|
|
|
'Android Mobile': /android\s(\d+(\.\d+)?)/,
|
|
|
|
|
'Internet Explorer': /(rv:|MSIE )(\d+(\.\d+)?)/,
|
|
|
|
|
'Mozilla': /rv:(\d+(\.\d+)?)/
|
|
|
|
|
};
|
|
|
|
|
var regex = versionRegexs[browser];
|
|
|
|
|
if (regex === undefined) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
var matches = userAgent.match(regex);
|
|
|
|
|
if (!matches) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return parseFloat(matches[matches.length - 2]);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
os: function() {
|
|
|
|
|
var a = userAgent;
|
|
|
|
|
if (/Windows/i.test(a)) {
|
|
|
|
|
if (/Phone/.test(a) || /WPDesktop/.test(a)) {
|
|
|
|
|
return 'Windows Phone';
|
|
|
|
|
}
|
|
|
|
|
return 'Windows';
|
|
|
|
|
} else if (/(iPhone|iPad|iPod)/.test(a)) {
|
|
|
|
|
return 'iOS';
|
|
|
|
|
} else if (/Android/.test(a)) {
|
|
|
|
|
return 'Android';
|
|
|
|
|
} else if (/(BlackBerry|PlayBook|BB10)/i.test(a)) {
|
|
|
|
|
return 'BlackBerry';
|
|
|
|
|
} else if (/Mac/i.test(a)) {
|
|
|
|
|
return 'Mac OS X';
|
|
|
|
|
} else if (/Linux/.test(a)) {
|
|
|
|
|
return 'Linux';
|
|
|
|
|
} else if (/CrOS/.test(a)) {
|
|
|
|
|
return 'Chrome OS';
|
|
|
|
|
} else {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
device: function(user_agent) {
|
|
|
|
|
if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) {
|
|
|
|
|
return 'Windows Phone';
|
|
|
|
|
} else if (/iPad/.test(user_agent)) {
|
|
|
|
|
return 'iPad';
|
|
|
|
|
} else if (/iPod/.test(user_agent)) {
|
|
|
|
|
return 'iPod Touch';
|
|
|
|
|
} else if (/iPhone/.test(user_agent)) {
|
|
|
|
|
return 'iPhone';
|
|
|
|
|
} else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) {
|
|
|
|
|
return 'BlackBerry';
|
|
|
|
|
} else if (/Android/.test(user_agent)) {
|
|
|
|
|
return 'Android';
|
|
|
|
|
} else {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
referringDomain: function(referrer) {
|
|
|
|
|
var split = referrer.split('/');
|
|
|
|
|
if (split.length >= 3) {
|
|
|
|
|
return split[2];
|
|
|
|
|
}
|
|
|
|
|
return '';
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
properties: function() {
|
|
|
|
|
return _.extend(_.strip_empty_properties({
|
|
|
|
|
'$os': _.info.os(),
|
|
|
|
|
'$browser': _.info.browser(userAgent, navigator$1.vendor, windowOpera),
|
|
|
|
|
'$referrer': document$1.referrer,
|
|
|
|
|
'$referring_domain': _.info.referringDomain(document$1.referrer),
|
|
|
|
|
'$device': _.info.device(userAgent)
|
|
|
|
|
}), {
|
|
|
|
|
'$current_url': window$1.location.href,
|
|
|
|
|
'$browser_version': _.info.browserVersion(userAgent, navigator$1.vendor, windowOpera),
|
|
|
|
|
'$screen_height': screen.height,
|
|
|
|
|
'$screen_width': screen.width,
|
|
|
|
|
'mp_lib': 'web',
|
|
|
|
|
'$lib_version': Config.LIB_VERSION
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
people_properties: function() {
|
|
|
|
|
return _.extend(_.strip_empty_properties({
|
|
|
|
|
'$os': _.info.os(),
|
|
|
|
|
'$browser': _.info.browser(userAgent, navigator$1.vendor, windowOpera)
|
|
|
|
|
}), {
|
|
|
|
|
'$browser_version': _.info.browserVersion(userAgent, navigator$1.vendor, windowOpera)
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
pageviewInfo: function(page) {
|
|
|
|
|
return _.strip_empty_properties({
|
|
|
|
|
'mp_page': page,
|
|
|
|
|
'mp_referrer': document$1.referrer,
|
|
|
|
|
'mp_browser': _.info.browser(userAgent, navigator$1.vendor, windowOpera),
|
|
|
|
|
'mp_platform': _.info.os()
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// EXPORTS (for closure compiler)
|
|
|
|
|
_['toArray'] = _.toArray;
|
|
|
|
|
_['isObject'] = _.isObject;
|
|
|
|
|
_['JSONEncode'] = _.JSONEncode;
|
|
|
|
|
_['JSONDecode'] = _.JSONDecode;
|
|
|
|
|
_['isBlockedUA'] = _.isBlockedUA;
|
|
|
|
|
_['isEmptyObject'] = _.isEmptyObject;
|
|
|
|
|
_['info'] = _.info;
|
|
|
|
|
_['info']['device'] = _.info.device;
|
|
|
|
|
_['info']['browser'] = _.info.browser;
|
|
|
|
|
_['info']['properties'] = _.info.properties;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Get the className of an element, accounting for edge cases where element.className is an object
|
|
|
|
|
* @param {Element} el - element to get the className of
|
|
|
|
|
* @returns {string} the element's class
|
|
|
|
|
*/
|
|
|
|
|
function getClassName(el) {
|
|
|
|
|
switch(typeof el.className) {
|
|
|
|
|
case 'string':
|
|
|
|
|
return el.className;
|
|
|
|
|
case 'object': // handle cases where className might be SVGAnimatedString or some other type
|
|
|
|
|
return el.className.baseVal || el.getAttribute('class') || '';
|
|
|
|
|
default: // future proof
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Get the direct text content of an element, protecting against sensitive data collection.
|
|
|
|
|
* Concats textContent of each of the element's text node children; this avoids potential
|
|
|
|
|
* collection of sensitive data that could happen if we used element.textContent and the
|
|
|
|
|
* element had sensitive child elements, since element.textContent includes child content.
|
|
|
|
|
* Scrubs values that look like they could be sensitive (i.e. cc or ssn number).
|
|
|
|
|
* @param {Element} el - element to get the text of
|
|
|
|
|
* @returns {string} the element's direct text content
|
|
|
|
|
*/
|
|
|
|
|
function getSafeText(el) {
|
|
|
|
|
var elText = '';
|
|
|
|
|
|
|
|
|
|
if (shouldTrackElement(el) && el.childNodes && el.childNodes.length) {
|
|
|
|
|
_.each(el.childNodes, function(child) {
|
|
|
|
|
if (isTextNode(child) && child.textContent) {
|
|
|
|
|
elText += _.trim(child.textContent)
|
|
|
|
|
// scrub potentially sensitive values
|
|
|
|
|
.split(/(\s+)/).filter(shouldTrackValue).join('')
|
|
|
|
|
// normalize whitespace
|
|
|
|
|
.replace(/[\r\n]/g, ' ').replace(/[ ]+/g, ' ')
|
|
|
|
|
// truncate
|
|
|
|
|
.substring(0, 255);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _.trim(elText);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Check whether an element has nodeType Node.ELEMENT_NODE
|
|
|
|
|
* @param {Element} el - element to check
|
|
|
|
|
* @returns {boolean} whether el is of the correct nodeType
|
|
|
|
|
*/
|
|
|
|
|
function isElementNode(el) {
|
|
|
|
|
return el && el.nodeType === 1; // Node.ELEMENT_NODE - use integer constant for browser portability
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Check whether an element is of a given tag type.
|
|
|
|
|
* Due to potential reference discrepancies (such as the webcomponents.js polyfill),
|
|
|
|
|
* we want to match tagNames instead of specific references because something like
|
|
|
|
|
* element === document.body won't always work because element might not be a native
|
|
|
|
|
* element.
|
|
|
|
|
* @param {Element} el - element to check
|
|
|
|
|
* @param {string} tag - tag name (e.g., "div")
|
|
|
|
|
* @returns {boolean} whether el is of the given tag type
|
|
|
|
|
*/
|
|
|
|
|
function isTag(el, tag) {
|
|
|
|
|
return el && el.tagName && el.tagName.toLowerCase() === tag.toLowerCase();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Check whether an element has nodeType Node.TEXT_NODE
|
|
|
|
|
* @param {Element} el - element to check
|
|
|
|
|
* @returns {boolean} whether el is of the correct nodeType
|
|
|
|
|
*/
|
|
|
|
|
function isTextNode(el) {
|
|
|
|
|
return el && el.nodeType === 3; // Node.TEXT_NODE - use integer constant for browser portability
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Check whether a DOM event should be "tracked" or if it may contain sentitive data
|
|
|
|
|
* using a variety of heuristics.
|
|
|
|
|
* @param {Element} el - element to check
|
|
|
|
|
* @param {Event} event - event to check
|
|
|
|
|
* @returns {boolean} whether the event should be tracked
|
|
|
|
|
*/
|
|
|
|
|
function shouldTrackDomEvent(el, event) {
|
|
|
|
|
if (!el || isTag(el, 'html') || !isElementNode(el)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
var tag = el.tagName.toLowerCase();
|
|
|
|
|
switch (tag) {
|
|
|
|
|
case 'html':
|
|
|
|
|
return false;
|
|
|
|
|
case 'form':
|
|
|
|
|
return event.type === 'submit';
|
|
|
|
|
case 'input':
|
|
|
|
|
if (['button', 'submit'].indexOf(el.getAttribute('type')) === -1) {
|
|
|
|
|
return event.type === 'change';
|
|
|
|
|
} else {
|
|
|
|
|
return event.type === 'click';
|
|
|
|
|
}
|
|
|
|
|
case 'select':
|
|
|
|
|
case 'textarea':
|
|
|
|
|
return event.type === 'change';
|
|
|
|
|
default:
|
|
|
|
|
return event.type === 'click';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Check whether a DOM element should be "tracked" or if it may contain sentitive data
|
|
|
|
|
* using a variety of heuristics.
|
|
|
|
|
* @param {Element} el - element to check
|
|
|
|
|
* @returns {boolean} whether the element should be tracked
|
|
|
|
|
*/
|
|
|
|
|
function shouldTrackElement(el) {
|
|
|
|
|
for (var curEl = el; curEl.parentNode && !isTag(curEl, 'body'); curEl = curEl.parentNode) {
|
|
|
|
|
var classes = getClassName(curEl).split(' ');
|
|
|
|
|
if (_.includes(classes, 'mp-sensitive') || _.includes(classes, 'mp-no-track')) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_.includes(getClassName(el).split(' '), 'mp-include')) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// don't send data from inputs or similar elements since there will always be
|
|
|
|
|
// a risk of clientside javascript placing sensitive data in attributes
|
|
|
|
|
if (
|
|
|
|
|
isTag(el, 'input') ||
|
|
|
|
|
isTag(el, 'select') ||
|
|
|
|
|
isTag(el, 'textarea') ||
|
|
|
|
|
el.getAttribute('contenteditable') === 'true'
|
|
|
|
|
) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// don't include hidden or password fields
|
|
|
|
|
var type = el.type || '';
|
|
|
|
|
if (typeof type === 'string') { // it's possible for el.type to be a DOM element if el is a form with a child input[name="type"]
|
|
|
|
|
switch(type.toLowerCase()) {
|
|
|
|
|
case 'hidden':
|
|
|
|
|
return false;
|
|
|
|
|
case 'password':
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// filter out data from fields that look like sensitive fields
|
|
|
|
|
var name = el.name || el.id || '';
|
|
|
|
|
if (typeof name === 'string') { // it's possible for el.name or el.id to be a DOM element if el is a form with a child input[name="name"]
|
|
|
|
|
var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
|
|
|
|
|
if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Check whether a string value should be "tracked" or if it may contain sentitive data
|
|
|
|
|
* using a variety of heuristics.
|
|
|
|
|
* @param {string} value - string value to check
|
|
|
|
|
* @returns {boolean} whether the element should be tracked
|
|
|
|
|
*/
|
|
|
|
|
function shouldTrackValue(value) {
|
|
|
|
|
if (value === null || _.isUndefined(value)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
|
value = _.trim(value);
|
|
|
|
|
|
|
|
|
|
// check to see if input value looks like a credit card number
|
|
|
|
|
// see: https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s20.html
|
|
|
|
|
var ccRegex = /^(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11}))$/;
|
|
|
|
|
if (ccRegex.test((value || '').replace(/[\- ]/g, ''))) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// check to see if input value looks like a social security number
|
|
|
|
|
var ssnRegex = /(^\d{3}-?\d{2}-?\d{4}$)/;
|
|
|
|
|
if (ssnRegex.test(value)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var autotrack = {
|
|
|
|
|
_initializedTokens: [],
|
|
|
|
|
|
|
|
|
|
_previousElementSibling: function(el) {
|
|
|
|
|
if (el.previousElementSibling) {
|
|
|
|
|
return el.previousElementSibling;
|
|
|
|
|
} else {
|
|
|
|
|
do {
|
|
|
|
|
el = el.previousSibling;
|
|
|
|
|
} while (el && !isElementNode(el));
|
|
|
|
|
return el;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_loadScript: function(scriptUrlToLoad, callback) {
|
|
|
|
|
var scriptTag = document.createElement('script');
|
|
|
|
|
scriptTag.type = 'text/javascript';
|
|
|
|
|
scriptTag.src = scriptUrlToLoad;
|
|
|
|
|
scriptTag.onload = callback;
|
|
|
|
|
|
|
|
|
|
var scripts = document.getElementsByTagName('script');
|
|
|
|
|
if (scripts.length > 0) {
|
|
|
|
|
scripts[0].parentNode.insertBefore(scriptTag, scripts[0]);
|
|
|
|
|
} else {
|
|
|
|
|
document.body.appendChild(scriptTag);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_getPropertiesFromElement: function(elem) {
|
|
|
|
|
var props = {
|
|
|
|
|
'classes': getClassName(elem).split(' '),
|
|
|
|
|
'tag_name': elem.tagName.toLowerCase()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (shouldTrackElement(elem)) {
|
|
|
|
|
_.each(elem.attributes, function(attr) {
|
|
|
|
|
if (shouldTrackValue(attr.value)) {
|
|
|
|
|
props['attr__' + attr.name] = attr.value;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var nthChild = 1;
|
|
|
|
|
var nthOfType = 1;
|
|
|
|
|
var currentElem = elem;
|
|
|
|
|
while (currentElem = this._previousElementSibling(currentElem)) { // eslint-disable-line no-cond-assign
|
|
|
|
|
nthChild++;
|
|
|
|
|
if (currentElem.tagName === elem.tagName) {
|
|
|
|
|
nthOfType++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
props['nth_child'] = nthChild;
|
|
|
|
|
props['nth_of_type'] = nthOfType;
|
|
|
|
|
|
|
|
|
|
return props;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_getDefaultProperties: function(eventType) {
|
|
|
|
|
return {
|
|
|
|
|
'$event_type': eventType,
|
|
|
|
|
'$ce_version': 1,
|
|
|
|
|
'$host': window.location.host,
|
|
|
|
|
'$pathname': window.location.pathname
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_extractCustomPropertyValue: function(customProperty) {
|
|
|
|
|
var propValues = [];
|
|
|
|
|
_.each(document.querySelectorAll(customProperty['css_selector']), function(matchedElem) {
|
|
|
|
|
var value;
|
|
|
|
|
|
|
|
|
|
if (['input', 'select'].indexOf(matchedElem.tagName.toLowerCase()) > -1) {
|
|
|
|
|
value = matchedElem['value'];
|
|
|
|
|
} else if (matchedElem['textContent']) {
|
|
|
|
|
value = matchedElem['textContent'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldTrackValue(value)) {
|
|
|
|
|
propValues.push(value);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return propValues.join(', ');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_getCustomProperties: function(targetElementList) {
|
|
|
|
|
var props = {};
|
|
|
|
|
_.each(this._customProperties, function(customProperty) {
|
|
|
|
|
_.each(customProperty['event_selectors'], function(eventSelector) {
|
|
|
|
|
var eventElements = document.querySelectorAll(eventSelector);
|
|
|
|
|
_.each(eventElements, function(eventElement) {
|
|
|
|
|
if (_.includes(targetElementList, eventElement) && shouldTrackElement(eventElement)) {
|
|
|
|
|
props[customProperty['name']] = this._extractCustomPropertyValue(customProperty);
|
|
|
|
|
}
|
|
|
|
|
}, this);
|
|
|
|
|
}, this);
|
|
|
|
|
}, this);
|
|
|
|
|
return props;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_getEventTarget: function(e) {
|
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/Event/target#Compatibility_notes
|
|
|
|
|
if (typeof e.target === 'undefined') {
|
|
|
|
|
return e.srcElement;
|
|
|
|
|
} else {
|
|
|
|
|
return e.target;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_trackEvent: function(e, instance) {
|
|
|
|
|
/*** Don't mess with this code without running IE8 tests on it ***/
|
|
|
|
|
var target = this._getEventTarget(e);
|
|
|
|
|
if (isTextNode(target)) { // defeat Safari bug (see: http://www.quirksmode.org/js/events_properties.html)
|
|
|
|
|
target = target.parentNode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldTrackDomEvent(target, e)) {
|
|
|
|
|
var targetElementList = [target];
|
|
|
|
|
var curEl = target;
|
|
|
|
|
while (curEl.parentNode && !isTag(curEl, 'body')) {
|
|
|
|
|
targetElementList.push(curEl.parentNode);
|
|
|
|
|
curEl = curEl.parentNode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var elementsJson = [];
|
|
|
|
|
var href, explicitNoTrack = false;
|
|
|
|
|
_.each(targetElementList, function(el) {
|
|
|
|
|
var shouldTrackEl = shouldTrackElement(el);
|
|
|
|
|
|
|
|
|
|
// if the element or a parent element is an anchor tag
|
|
|
|
|
// include the href as a property
|
|
|
|
|
if (el.tagName.toLowerCase() === 'a') {
|
|
|
|
|
href = el.getAttribute('href');
|
|
|
|
|
href = shouldTrackEl && shouldTrackValue(href) && href;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// allow users to programatically prevent tracking of elements by adding class 'mp-no-track'
|
|
|
|
|
var classes = getClassName(el).split(' ');
|
|
|
|
|
if (_.includes(classes, 'mp-no-track')) {
|
|
|
|
|
explicitNoTrack = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elementsJson.push(this._getPropertiesFromElement(el));
|
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
|
|
if (explicitNoTrack) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// only populate text content from target element (not parents)
|
|
|
|
|
// to prevent text within a sensitive element from being collected
|
|
|
|
|
// as part of a parent's el.textContent
|
|
|
|
|
var elementText;
|
|
|
|
|
var safeElementText = getSafeText(target);
|
|
|
|
|
if (safeElementText && safeElementText.length) {
|
|
|
|
|
elementText = safeElementText;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var props = _.extend(
|
|
|
|
|
this._getDefaultProperties(e.type),
|
|
|
|
|
{
|
|
|
|
|
'$elements': elementsJson,
|
|
|
|
|
'$el_attr__href': href,
|
|
|
|
|
'$el_text': elementText
|
|
|
|
|
},
|
|
|
|
|
this._getCustomProperties(targetElementList)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
instance.track('$web_event', props);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// only reason is to stub for unit tests
|
|
|
|
|
// since you can't override window.location props
|
|
|
|
|
_navigate: function(href) {
|
|
|
|
|
window.location.href = href;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_addDomEventHandlers: function(instance) {
|
|
|
|
|
var handler = _.bind(function(e) {
|
|
|
|
|
e = e || window.event;
|
|
|
|
|
this._trackEvent(e, instance);
|
|
|
|
|
}, this);
|
|
|
|
|
_.register_event(document, 'submit', handler, false, true);
|
|
|
|
|
_.register_event(document, 'change', handler, false, true);
|
|
|
|
|
_.register_event(document, 'click', handler, false, true);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_customProperties: {},
|
|
|
|
|
init: function(instance) {
|
|
|
|
|
if (!(document && document.body)) {
|
|
|
|
|
console.log('document not ready yet, trying again in 500 milliseconds...');
|
|
|
|
|
var that = this;
|
|
|
|
|
setTimeout(function() { that.init(instance); }, 500);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var token = instance.get_config('token');
|
|
|
|
|
if (this._initializedTokens.indexOf(token) > -1) {
|
|
|
|
|
console.log('autotrack already initialized for token "' + token + '"');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this._initializedTokens.push(token);
|
|
|
|
|
|
|
|
|
|
if (!this._maybeLoadEditor(instance)) { // don't autotrack actions when the editor is enabled
|
|
|
|
|
var parseDecideResponse = _.bind(function(response) {
|
|
|
|
|
if (response && response['config'] && response['config']['enable_collect_everything'] === true) {
|
|
|
|
|
|
|
|
|
|
if (response['custom_properties']) {
|
|
|
|
|
this._customProperties = response['custom_properties'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
instance.track('$web_event', _.extend({
|
|
|
|
|
'$title': document.title
|
|
|
|
|
}, this._getDefaultProperties('pageview')));
|
|
|
|
|
|
|
|
|
|
this._addDomEventHandlers(instance);
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
instance['__autotrack_enabled'] = false;
|
|
|
|
|
}
|
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
|
|
instance._send_request(
|
|
|
|
|
instance.get_config('api_host') + '/decide/', {
|
|
|
|
|
'verbose': true,
|
|
|
|
|
'version': '1',
|
|
|
|
|
'lib': 'web',
|
|
|
|
|
'token': token
|
|
|
|
|
},
|
|
|
|
|
instance._prepare_callback(parseDecideResponse)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_editorParamsFromHash: function(instance, hash) {
|
|
|
|
|
var editorParams;
|
|
|
|
|
try {
|
|
|
|
|
var state = _.getHashParam(hash, 'state');
|
|
|
|
|
state = JSON.parse(decodeURIComponent(state));
|
|
|
|
|
var expiresInSeconds = _.getHashParam(hash, 'expires_in');
|
|
|
|
|
editorParams = {
|
|
|
|
|
'accessToken': _.getHashParam(hash, 'access_token'),
|
|
|
|
|
'accessTokenExpiresAt': (new Date()).getTime() + (Number(expiresInSeconds) * 1000),
|
|
|
|
|
'bookmarkletMode': !!state['bookmarkletMode'],
|
|
|
|
|
'projectId': state['projectId'],
|
|
|
|
|
'projectOwnerId': state['projectOwnerId'],
|
|
|
|
|
'projectToken': state['token'],
|
|
|
|
|
'readOnly': state['readOnly'],
|
|
|
|
|
'userFlags': state['userFlags'],
|
|
|
|
|
'userId': state['userId']
|
|
|
|
|
};
|
|
|
|
|
window.sessionStorage.setItem('editorParams', JSON.stringify(editorParams));
|
|
|
|
|
|
|
|
|
|
if (state['desiredHash']) {
|
|
|
|
|
window.location.hash = state['desiredHash'];
|
|
|
|
|
} else if (window.history) {
|
|
|
|
|
history.replaceState('', document.title, window.location.pathname + window.location.search); // completely remove hash
|
|
|
|
|
} else {
|
|
|
|
|
window.location.hash = ''; // clear hash (but leaves # unfortunately)
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('Unable to parse data from hash', e);
|
|
|
|
|
}
|
|
|
|
|
return editorParams;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* To load the visual editor, we need an access token and other state. That state comes from one of three places:
|
|
|
|
|
* 1. In the URL hash params if the customer is using an old snippet
|
|
|
|
|
* 2. From session storage under the key `_mpcehash` if the snippet already parsed the hash
|
|
|
|
|
* 3. From session storage under the key `editorParams` if the editor was initialized on a previous page
|
|
|
|
|
*/
|
|
|
|
|
_maybeLoadEditor: function(instance) {
|
|
|
|
|
try {
|
|
|
|
|
var parseFromUrl = false;
|
|
|
|
|
if (_.getHashParam(window.location.hash, 'state')) {
|
|
|
|
|
var state = _.getHashParam(window.location.hash, 'state');
|
|
|
|
|
state = JSON.parse(decodeURIComponent(state));
|
|
|
|
|
parseFromUrl = state['action'] === 'mpeditor';
|
|
|
|
|
}
|
|
|
|
|
var parseFromStorage = !!window.sessionStorage.getItem('_mpcehash');
|
|
|
|
|
var editorParams;
|
|
|
|
|
|
|
|
|
|
if (parseFromUrl) { // happens if they are initializing the editor using an old snippet
|
|
|
|
|
editorParams = this._editorParamsFromHash(instance, window.location.hash);
|
|
|
|
|
} else if (parseFromStorage) { // happens if they are initialized the editor and using the new snippet
|
|
|
|
|
editorParams = this._editorParamsFromHash(instance, window.sessionStorage.getItem('_mpcehash'));
|
|
|
|
|
window.sessionStorage.removeItem('_mpcehash');
|
|
|
|
|
} else { // get credentials from sessionStorage from a previous initialzation
|
|
|
|
|
editorParams = JSON.parse(window.sessionStorage.getItem('editorParams') || '{}');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (editorParams['projectToken'] && instance.get_config('token') === editorParams['projectToken']) {
|
|
|
|
|
this._loadEditor(instance, editorParams);
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_loadEditor: function(instance, editorParams) {
|
|
|
|
|
if (!window['_mpEditorLoaded']) { // only load the codeless event editor once, even if there are multiple instances of MixpanelLib
|
|
|
|
|
window['_mpEditorLoaded'] = true;
|
|
|
|
|
var editorUrl = instance.get_config('app_host')
|
|
|
|
|
+ '/js-bundle/reports/collect-everything/editor.js?_ts='
|
|
|
|
|
+ (new Date()).getTime();
|
|
|
|
|
this._loadScript(editorUrl, function() {
|
|
|
|
|
window['mp_load_editor'](editorParams);
|
|
|
|
|
});
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// this is a mechanism to ramp up CE with no server-side interaction.
|
|
|
|
|
// when CE is active, every page load results in a decide request. we
|
|
|
|
|
// need to gently ramp this up so we don't overload decide. this decides
|
|
|
|
|
// deterministically if CE is enabled for this project by modding the char
|
|
|
|
|
// value of the project token.
|
|
|
|
|
enabledForProject: function(token, numBuckets, numEnabledBuckets) {
|
|
|
|
|
numBuckets = !_.isUndefined(numBuckets) ? numBuckets : 10;
|
|
|
|
|
numEnabledBuckets = !_.isUndefined(numEnabledBuckets) ? numEnabledBuckets : 10;
|
|
|
|
|
var charCodeSum = 0;
|
|
|
|
|
for (var i = 0; i < token.length; i++) {
|
|
|
|
|
charCodeSum += token.charCodeAt(i);
|
|
|
|
|
}
|
|
|
|
|
return (charCodeSum % numBuckets) < numEnabledBuckets;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
isBrowserSupported: function() {
|
|
|
|
|
return _.isFunction(document.querySelectorAll);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_.bind_instance_methods(autotrack);
|
|
|
|
|
_.safewrap_instance_methods(autotrack);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A function used to track a Mixpanel event (e.g. MixpanelLib.track)
|
|
|
|
|
* @callback trackFunction
|
|
|
|
|
* @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc.
|
|
|
|
|
* @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself.
|
|
|
|
|
* @param {Function} [callback] If provided, the callback function will be called after tracking the event.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/** Public **/
|
|
|
|
|
|
|
|
|
|
var GDPR_DEFAULT_PERSISTENCE_PREFIX = '__mp_opt_in_out_';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Opt the user in to data tracking and cookies/localstorage for the given token
|
|
|
|
|
* @param {string} token - Mixpanel project tracking token
|
|
|
|
|
* @param {Object} [options]
|
|
|
|
|
* @param {trackFunction} [options.track] - function used for tracking a Mixpanel event to record the opt-in action
|
|
|
|
|
* @param {string} [options.trackEventName] - event name to be used for tracking the opt-in action
|
|
|
|
|
* @param {Object} [options.trackProperties] - set of properties to be tracked along with the opt-in action
|
|
|
|
|
* @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage
|
|
|
|
|
* @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name
|
|
|
|
|
* @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires
|
|
|
|
|
* @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not
|
|
|
|
|
* @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not
|
|
|
|
|
*/
|
|
|
|
|
function optIn(token, options) {
|
|
|
|
|
_optInOut(true, token, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Opt the user out of data tracking and cookies/localstorage for the given token
|
|
|
|
|
* @param {string} token - Mixpanel project tracking token
|
|
|
|
|
* @param {Object} [options]
|
|
|
|
|
* @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage
|
|
|
|
|
* @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name
|
|
|
|
|
* @param {Number} [options.cookieExpiration] - number of days until the opt-out cookie expires
|
|
|
|
|
* @param {boolean} [options.crossSubdomainCookie] - whether the opt-out cookie is set as cross-subdomain or not
|
|
|
|
|
* @param {boolean} [options.secureCookie] - whether the opt-out cookie is set as secure or not
|
|
|
|
|
*/
|
|
|
|
|
function optOut(token, options) {
|
|
|
|
|
_optInOut(false, token, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check whether the user has opted in to data tracking and cookies/localstorage for the given token
|
|
|
|
|
* @param {string} token - Mixpanel project tracking token
|
|
|
|
|
* @param {Object} [options]
|
|
|
|
|
* @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage
|
|
|
|
|
* @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name
|
|
|
|
|
* @returns {boolean} whether the user has opted in to the given opt type
|
|
|
|
|
*/
|
|
|
|
|
function hasOptedIn(token, options) {
|
|
|
|
|
return _getStorageValue(token, options) === '1';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check whether the user has opted out of data tracking and cookies/localstorage for the given token
|
|
|
|
|
* @param {string} token - Mixpanel project tracking token
|
|
|
|
|
* @param {Object} [options]
|
|
|
|
|
* @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage
|
|
|
|
|
* @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name
|
|
|
|
|
* @returns {boolean} whether the user has opted out of the given opt type
|
|
|
|
|
*/
|
|
|
|
|
function hasOptedOut(token, options) {
|
|
|
|
|
if (_hasDoNotTrackFlagOn()) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return _getStorageValue(token, options) === '0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Wrap a MixpanelLib method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token
|
|
|
|
|
* If the user has opted out, return early instead of executing the method.
|
|
|
|
|
* If a callback argument was provided, execute it passing the 0 error code.
|
|
|
|
|
* @param {function} method - wrapped method to be executed if the user has not opted out
|
|
|
|
|
* @returns {*} the result of executing method OR undefined if the user has opted out
|
|
|
|
|
*/
|
|
|
|
|
function addOptOutCheckMixpanelLib(method) {
|
|
|
|
|
return _addOptOutCheck(method, function(name) {
|
|
|
|
|
return this.get_config(name);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Wrap a MixpanelPeople method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token
|
|
|
|
|
* If the user has opted out, return early instead of executing the method.
|
|
|
|
|
* If a callback argument was provided, execute it passing the 0 error code.
|
|
|
|
|
* @param {function} method - wrapped method to be executed if the user has not opted out
|
|
|
|
|
* @returns {*} the result of executing method OR undefined if the user has opted out
|
|
|
|
|
*/
|
|
|
|
|
function addOptOutCheckMixpanelPeople(method) {
|
|
|
|
|
return _addOptOutCheck(method, function(name) {
|
|
|
|
|
return this._get_config(name);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clear the user's opt in/out status of data tracking and cookies/localstorage for the given token
|
|
|
|
|
* @param {string} token - Mixpanel project tracking token
|
|
|
|
|
* @param {Object} [options]
|
|
|
|
|
* @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage
|
|
|
|
|
* @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name
|
|
|
|
|
* @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires
|
|
|
|
|
* @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not
|
|
|
|
|
* @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not
|
|
|
|
|
*/
|
|
|
|
|
function clearOptInOut(token, options) {
|
|
|
|
|
options = options || {};
|
|
|
|
|
_getStorage(options).remove(_getStorageKey(token, options), !!options.crossSubdomainCookie);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Private **/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get storage util
|
|
|
|
|
* @param {Object} [options]
|
|
|
|
|
* @param {string} [options.persistenceType]
|
|
|
|
|
* @returns {object} either _.cookie or _.localstorage
|
|
|
|
|
*/
|
|
|
|
|
function _getStorage(options) {
|
|
|
|
|
options = options || {};
|
|
|
|
|
return options.persistenceType === 'localStorage' ? _.localStorage : _.cookie;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the name of the cookie that is used for the given opt type (tracking, cookie, etc.)
|
|
|
|
|
* @param {string} token - Mixpanel project tracking token
|
|
|
|
|
* @param {Object} [options]
|
|
|
|
|
* @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name
|
|
|
|
|
* @returns {string} the name of the cookie for the given opt type
|
|
|
|
|
*/
|
|
|
|
|
function _getStorageKey(token, options) {
|
|
|
|
|
options = options || {};
|
|
|
|
|
return (options.persistencePrefix || GDPR_DEFAULT_PERSISTENCE_PREFIX) + token;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the value of the cookie that is used for the given opt type (tracking, cookie, etc.)
|
|
|
|
|
* @param {string} token - Mixpanel project tracking token
|
|
|
|
|
* @param {Object} [options]
|
|
|
|
|
* @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name
|
|
|
|
|
* @returns {string} the value of the cookie for the given opt type
|
|
|
|
|
*/
|
|
|
|
|
function _getStorageValue(token, options) {
|
|
|
|
|
return _getStorage(options).get(_getStorageKey(token, options));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check whether the user has set the DNT/doNotTrack setting to true in their browser
|
|
|
|
|
* @returns {boolean} whether the DNT setting is true
|
|
|
|
|
*/
|
|
|
|
|
function _hasDoNotTrackFlagOn() {
|
|
|
|
|
return !!(window$1.navigator && window$1.navigator.doNotTrack === '1');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set cookie/localstorage for the user indicating that they are opted in or out for the given opt type
|
|
|
|
|
* @param {boolean} optValue - whether to opt the user in or out for the given opt type
|
|
|
|
|
* @param {string} token - Mixpanel project tracking token
|
|
|
|
|
* @param {Object} [options]
|
|
|
|
|
* @param {trackFunction} [options.track] - function used for tracking a Mixpanel event to record the opt-in action
|
|
|
|
|
* @param {string} [options.trackEventName] - event name to be used for tracking the opt-in action
|
|
|
|
|
* @param {Object} [options.trackProperties] - set of properties to be tracked along with the opt-in action
|
|
|
|
|
* @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name
|
|
|
|
|
* @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires
|
|
|
|
|
* @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not
|
|
|
|
|
* @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not
|
|
|
|
|
*/
|
|
|
|
|
function _optInOut(optValue, token, options) {
|
|
|
|
|
if (!_.isString(token) || !token.length) {
|
|
|
|
|
console.error('gdpr.' + (optValue ? 'optIn' : 'optOut') + ' called with an invalid token');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
options = options || {};
|
|
|
|
|
|
|
|
|
|
_getStorage(options).set(
|
|
|
|
|
_getStorageKey(token, options),
|
|
|
|
|
optValue ? 1 : 0,
|
|
|
|
|
_.isNumber(options.cookieExpiration) ? options.cookieExpiration : null,
|
|
|
|
|
!!options.crossSubdomainCookie,
|
|
|
|
|
!!options.secureCookie
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (options.track && optValue) { // only track event if opting in (optValue=true)
|
|
|
|
|
options.track(options.trackEventName || '$opt_in', options.trackProperties);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Wrap a method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token
|
|
|
|
|
* If the user has opted out, return early instead of executing the method.
|
|
|
|
|
* If a callback argument was provided, execute it passing the 0 error code.
|
|
|
|
|
* @param {function} method - wrapped method to be executed if the user has not opted out
|
|
|
|
|
* @param {function} getConfigValue - getter function for the Mixpanel API token and other options to be used with opt-out check
|
|
|
|
|
* @returns {*} the result of executing method OR undefined if the user has opted out
|
|
|
|
|
*/
|
|
|
|
|
function _addOptOutCheck(method, getConfigValue) {
|
|
|
|
|
return function() {
|
|
|
|
|
var optedOut = false;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
var token = getConfigValue.call(this, 'token');
|
|
|
|
|
var persistenceType = getConfigValue.call(this, 'opt_out_tracking_persistence_type');
|
|
|
|
|
var persistencePrefix = getConfigValue.call(this, 'opt_out_tracking_cookie_prefix');
|
|
|
|
|
|
|
|
|
|
if (token) { // if there was an issue getting the token, continue method execution as normal
|
|
|
|
|
optedOut = hasOptedOut(token, {
|
|
|
|
|
persistenceType: persistenceType,
|
|
|
|
|
persistencePrefix: persistencePrefix
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch(err) {
|
|
|
|
|
console.error('Unexpected error when checking tracking opt-out status: ' + err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!optedOut) {
|
|
|
|
|
return method.apply(this, arguments);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var callback = arguments[arguments.length - 1];
|
|
|
|
|
if (typeof(callback) === 'function') {
|
|
|
|
|
callback(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Mixpanel JS Library
|
|
|
|
|
*
|
|
|
|
|
* Copyright 2012, Mixpanel, Inc. All Rights Reserved
|
|
|
|
|
* http://mixpanel.com/
|
|
|
|
|
*
|
|
|
|
|
* Includes portions of Underscore.js
|
|
|
|
|
* http://documentcloud.github.com/underscore/
|
|
|
|
|
* (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
|
|
|
|
|
* Released under the MIT License.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// ==ClosureCompiler==
|
|
|
|
|
// @compilation_level ADVANCED_OPTIMIZATIONS
|
|
|
|
|
// @output_file_name mixpanel-2.8.min.js
|
|
|
|
|
// ==/ClosureCompiler==
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
SIMPLE STYLE GUIDE:
|
|
|
|
|
|
|
|
|
|
this.x === public function
|
|
|
|
|
this._x === internal - only use within this file
|
|
|
|
|
this.__x === private - only use within the class
|
|
|
|
|
|
|
|
|
|
Globals should be all caps
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
var init_type; // MODULE or SNIPPET loader
|
|
|
|
|
var mixpanel_master; // main mixpanel instance / object
|
|
|
|
|
var INIT_MODULE = 0;
|
|
|
|
|
var INIT_SNIPPET = 1;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Constants
|
|
|
|
|
*/
|
|
|
|
|
/** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel';
|
|
|
|
|
/** @const */ var SET_QUEUE_KEY = '__mps';
|
|
|
|
|
/** @const */ var SET_ONCE_QUEUE_KEY = '__mpso';
|
|
|
|
|
/** @const */ var UNSET_QUEUE_KEY = '__mpus';
|
|
|
|
|
/** @const */ var ADD_QUEUE_KEY = '__mpa';
|
|
|
|
|
/** @const */ var APPEND_QUEUE_KEY = '__mpap';
|
|
|
|
|
/** @const */ var UNION_QUEUE_KEY = '__mpu';
|
|
|
|
|
/** @const */ var SET_ACTION = '$set';
|
|
|
|
|
/** @const */ var SET_ONCE_ACTION = '$set_once';
|
|
|
|
|
/** @const */ var UNSET_ACTION = '$unset';
|
|
|
|
|
/** @const */ var ADD_ACTION = '$add';
|
|
|
|
|
/** @const */ var APPEND_ACTION = '$append';
|
|
|
|
|
/** @const */ var UNION_ACTION = '$union';
|
|
|
|
|
// This key is deprecated, but we want to check for it to see whether aliasing is allowed.
|
|
|
|
|
/** @const */ var PEOPLE_DISTINCT_ID_KEY = '$people_distinct_id';
|
|
|
|
|
/** @const */ var ALIAS_ID_KEY = '__alias';
|
|
|
|
|
/** @const */ var CAMPAIGN_IDS_KEY = '__cmpns';
|
|
|
|
|
/** @const */ var EVENT_TIMERS_KEY = '__timers';
|
|
|
|
|
/** @const */ var RESERVED_PROPERTIES = [
|
|
|
|
|
SET_QUEUE_KEY,
|
|
|
|
|
SET_ONCE_QUEUE_KEY,
|
|
|
|
|
UNSET_QUEUE_KEY,
|
|
|
|
|
ADD_QUEUE_KEY,
|
|
|
|
|
APPEND_QUEUE_KEY,
|
|
|
|
|
UNION_QUEUE_KEY,
|
|
|
|
|
PEOPLE_DISTINCT_ID_KEY,
|
|
|
|
|
ALIAS_ID_KEY,
|
|
|
|
|
CAMPAIGN_IDS_KEY,
|
|
|
|
|
EVENT_TIMERS_KEY
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Dynamic... constants? Is that an oxymoron?
|
|
|
|
|
*/
|
|
|
|
|
// http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/
|
|
|
|
|
// https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#withCredentials
|
|
|
|
|
var USE_XHR = (window$1.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest());
|
|
|
|
|
|
|
|
|
|
// IE<10 does not support cross-origin XHR's but script tags
|
|
|
|
|
// with defer won't block window.onload; ENQUEUE_REQUESTS
|
|
|
|
|
// should only be true for Opera<12
|
|
|
|
|
var ENQUEUE_REQUESTS = !USE_XHR && (userAgent.indexOf('MSIE') === -1) && (userAgent.indexOf('Mozilla') === -1);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Module-level globals
|
|
|
|
|
*/
|
|
|
|
|
var DEFAULT_CONFIG = {
|
|
|
|
|
'api_host': 'https://api.mixpanel.com',
|
|
|
|
|
'app_host': 'https://mixpanel.com',
|
|
|
|
|
'autotrack': true,
|
|
|
|
|
'cdn': 'https://cdn.mxpnl.com',
|
|
|
|
|
'cross_subdomain_cookie': true,
|
|
|
|
|
'persistence': 'cookie',
|
|
|
|
|
'persistence_name': '',
|
|
|
|
|
'cookie_name': '',
|
|
|
|
|
'loaded': function() {},
|
|
|
|
|
'store_google': true,
|
|
|
|
|
'save_referrer': true,
|
|
|
|
|
'test': false,
|
|
|
|
|
'verbose': false,
|
|
|
|
|
'img': false,
|
|
|
|
|
'track_pageview': true,
|
|
|
|
|
'debug': false,
|
|
|
|
|
'track_links_timeout': 300,
|
|
|
|
|
'cookie_expiration': 365,
|
|
|
|
|
'upgrade': false,
|
|
|
|
|
'disable_persistence': false,
|
|
|
|
|
'disable_cookie': false,
|
|
|
|
|
'secure_cookie': false,
|
|
|
|
|
'ip': true,
|
|
|
|
|
'opt_out_tracking_by_default': false,
|
|
|
|
|
'opt_out_tracking_persistence_type': 'localStorage',
|
|
|
|
|
'opt_out_tracking_cookie_prefix': null,
|
|
|
|
|
'property_blacklist': [],
|
|
|
|
|
'xhr_headers': {} // { header: value, header2: value }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var DOM_LOADED = false;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* DomTracker Object
|
|
|
|
|
* @constructor
|
|
|
|
|
*/
|
|
|
|
|
var DomTracker = function() {};
|
|
|
|
|
|
|
|
|
|
// interface
|
|
|
|
|
DomTracker.prototype.create_properties = function() {};
|
|
|
|
|
DomTracker.prototype.event_handler = function() {};
|
|
|
|
|
DomTracker.prototype.after_track_handler = function() {};
|
|
|
|
|
|
|
|
|
|
DomTracker.prototype.init = function(mixpanel_instance) {
|
|
|
|
|
this.mp = mixpanel_instance;
|
|
|
|
|
return this;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {Object|string} query
|
|
|
|
|
* @param {string} event_name
|
|
|
|
|
* @param {Object=} properties
|
|
|
|
|
* @param {function(...[*])=} user_callback
|
|
|
|
|
*/
|
|
|
|
|
DomTracker.prototype.track = function(query, event_name, properties, user_callback) {
|
|
|
|
|
var that = this;
|
|
|
|
|
var elements = _.dom_query(query);
|
|
|
|
|
|
|
|
|
|
if (elements.length === 0) {
|
|
|
|
|
console$1.error('The DOM query (' + query + ') returned 0 elements');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_.each(elements, function(element) {
|
|
|
|
|
_.register_event(element, this.override_event, function(e) {
|
|
|
|
|
var options = {};
|
|
|
|
|
var props = that.create_properties(properties, this);
|
|
|
|
|
var timeout = that.mp.get_config('track_links_timeout');
|
|
|
|
|
|
|
|
|
|
that.event_handler(e, this, options);
|
|
|
|
|
|
|
|
|
|
// in case the mixpanel servers don't get back to us in time
|
|
|
|
|
window$1.setTimeout(that.track_callback(user_callback, props, options, true), timeout);
|
|
|
|
|
|
|
|
|
|
// fire the tracking event
|
|
|
|
|
that.mp.track(event_name, props, that.track_callback(user_callback, props, options));
|
|
|
|
|
});
|
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {function(...[*])} user_callback
|
|
|
|
|
* @param {Object} props
|
|
|
|
|
* @param {boolean=} timeout_occured
|
|
|
|
|
*/
|
|
|
|
|
DomTracker.prototype.track_callback = function(user_callback, props, options, timeout_occured) {
|
|
|
|
|
timeout_occured = timeout_occured || false;
|
|
|
|
|
var that = this;
|
|
|
|
|
|
|
|
|
|
return function() {
|
|
|
|
|
// options is referenced from both callbacks, so we can have
|
|
|
|
|
// a 'lock' of sorts to ensure only one fires
|
|
|
|
|
if (options.callback_fired) { return; }
|
|
|
|
|
options.callback_fired = true;
|
|
|
|
|
|
|
|
|
|
if (user_callback && user_callback(timeout_occured, props) === false) {
|
|
|
|
|
// user can prevent the default functionality by
|
|
|
|
|
// returning false from their callback
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
that.after_track_handler(props, options, timeout_occured);
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
DomTracker.prototype.create_properties = function(properties, element) {
|
|
|
|
|
var props;
|
|
|
|
|
|
|
|
|
|
if (typeof(properties) === 'function') {
|
|
|
|
|
props = properties(element);
|
|
|
|
|
} else {
|
|
|
|
|
props = _.extend({}, properties);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return props;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* LinkTracker Object
|
|
|
|
|
* @constructor
|
|
|
|
|
* @extends DomTracker
|
|
|
|
|
*/
|
|
|
|
|
var LinkTracker = function() {
|
|
|
|
|
this.override_event = 'click';
|
|
|
|
|
};
|
|
|
|
|
_.inherit(LinkTracker, DomTracker);
|
|
|
|
|
|
|
|
|
|
LinkTracker.prototype.create_properties = function(properties, element) {
|
|
|
|
|
var props = LinkTracker.superclass.create_properties.apply(this, arguments);
|
|
|
|
|
|
|
|
|
|
if (element.href) { props['url'] = element.href; }
|
|
|
|
|
|
|
|
|
|
return props;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
LinkTracker.prototype.event_handler = function(evt, element, options) {
|
|
|
|
|
options.new_tab = (
|
|
|
|
|
evt.which === 2 ||
|
|
|
|
|
evt.metaKey ||
|
|
|
|
|
evt.ctrlKey ||
|
|
|
|
|
element.target === '_blank'
|
|
|
|
|
);
|
|
|
|
|
options.href = element.href;
|
|
|
|
|
|
|
|
|
|
if (!options.new_tab) {
|
|
|
|
|
evt.preventDefault();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
LinkTracker.prototype.after_track_handler = function(props, options) {
|
|
|
|
|
if (options.new_tab) { return; }
|
|
|
|
|
|
|
|
|
|
setTimeout(function() {
|
|
|
|
|
window$1.location = options.href;
|
|
|
|
|
}, 0);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* FormTracker Object
|
|
|
|
|
* @constructor
|
|
|
|
|
* @extends DomTracker
|
|
|
|
|
*/
|
|
|
|
|
var FormTracker = function() {
|
|
|
|
|
this.override_event = 'submit';
|
|
|
|
|
};
|
|
|
|
|
_.inherit(FormTracker, DomTracker);
|
|
|
|
|
|
|
|
|
|
FormTracker.prototype.event_handler = function(evt, element, options) {
|
|
|
|
|
options.element = element;
|
|
|
|
|
evt.preventDefault();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
FormTracker.prototype.after_track_handler = function(props, options) {
|
|
|
|
|
setTimeout(function() {
|
|
|
|
|
options.element.submit();
|
|
|
|
|
}, 0);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Mixpanel Persistence Object
|
|
|
|
|
* @constructor
|
|
|
|
|
*/
|
|
|
|
|
var MixpanelPersistence = function(config) {
|
|
|
|
|
this['props'] = {};
|
|
|
|
|
this.campaign_params_saved = false;
|
|
|
|
|
|
|
|
|
|
if (config['persistence_name']) {
|
|
|
|
|
this.name = 'mp_' + config['persistence_name'];
|
|
|
|
|
} else {
|
|
|
|
|
this.name = 'mp_' + config['token'] + '_mixpanel';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var storage_type = config['persistence'];
|
|
|
|
|
if (storage_type !== 'cookie' && storage_type !== 'localStorage') {
|
|
|
|
|
console$1.critical('Unknown persistence type ' + storage_type + '; falling back to cookie');
|
|
|
|
|
storage_type = config['persistence'] = 'cookie';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (storage_type === 'localStorage' && _.localStorage.is_supported()) {
|
|
|
|
|
this.storage = _.localStorage;
|
|
|
|
|
} else {
|
|
|
|
|
this.storage = _.cookie;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.load();
|
|
|
|
|
this.update_config(config);
|
|
|
|
|
this.upgrade(config);
|
|
|
|
|
this.save();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype.properties = function() {
|
|
|
|
|
var p = {};
|
|
|
|
|
// Filter out reserved properties
|
|
|
|
|
_.each(this['props'], function(v, k) {
|
|
|
|
|
if (!_.include(RESERVED_PROPERTIES, k)) {
|
|
|
|
|
p[k] = v;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return p;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype.load = function() {
|
|
|
|
|
if (this.disabled) { return; }
|
|
|
|
|
|
|
|
|
|
var entry = this.storage.parse(this.name);
|
|
|
|
|
|
|
|
|
|
if (entry) {
|
|
|
|
|
this['props'] = _.extend({}, entry);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype.upgrade = function(config) {
|
|
|
|
|
var upgrade_from_old_lib = config['upgrade'],
|
|
|
|
|
old_cookie_name,
|
|
|
|
|
old_cookie;
|
|
|
|
|
|
|
|
|
|
if (upgrade_from_old_lib) {
|
|
|
|
|
old_cookie_name = 'mp_super_properties';
|
|
|
|
|
// Case where they had a custom cookie name before.
|
|
|
|
|
if (typeof(upgrade_from_old_lib) === 'string') {
|
|
|
|
|
old_cookie_name = upgrade_from_old_lib;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
old_cookie = this.storage.parse(old_cookie_name);
|
|
|
|
|
|
|
|
|
|
// remove the cookie
|
|
|
|
|
this.storage.remove(old_cookie_name);
|
|
|
|
|
this.storage.remove(old_cookie_name, true);
|
|
|
|
|
|
|
|
|
|
if (old_cookie) {
|
|
|
|
|
this['props'] = _.extend(
|
|
|
|
|
this['props'],
|
|
|
|
|
old_cookie['all'],
|
|
|
|
|
old_cookie['events']
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!config['cookie_name'] && config['name'] !== 'mixpanel') {
|
|
|
|
|
// special case to handle people with cookies of the form
|
|
|
|
|
// mp_TOKEN_INSTANCENAME from the first release of this library
|
|
|
|
|
old_cookie_name = 'mp_' + config['token'] + '_' + config['name'];
|
|
|
|
|
old_cookie = this.storage.parse(old_cookie_name);
|
|
|
|
|
|
|
|
|
|
if (old_cookie) {
|
|
|
|
|
this.storage.remove(old_cookie_name);
|
|
|
|
|
this.storage.remove(old_cookie_name, true);
|
|
|
|
|
|
|
|
|
|
// Save the prop values that were in the cookie from before -
|
|
|
|
|
// this should only happen once as we delete the old one.
|
|
|
|
|
this.register_once(old_cookie);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.storage === _.localStorage) {
|
|
|
|
|
old_cookie = _.cookie.parse(this.name);
|
|
|
|
|
|
|
|
|
|
_.cookie.remove(this.name);
|
|
|
|
|
_.cookie.remove(this.name, true);
|
|
|
|
|
|
|
|
|
|
if (old_cookie) {
|
|
|
|
|
this.register_once(old_cookie);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype.save = function() {
|
|
|
|
|
if (this.disabled) { return; }
|
|
|
|
|
this._expire_notification_campaigns();
|
|
|
|
|
this.storage.set(
|
|
|
|
|
this.name,
|
|
|
|
|
_.JSONEncode(this['props']),
|
|
|
|
|
this.expire_days,
|
|
|
|
|
this.cross_subdomain,
|
|
|
|
|
this.secure
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype.remove = function() {
|
|
|
|
|
// remove both domain and subdomain cookies
|
|
|
|
|
this.storage.remove(this.name, false);
|
|
|
|
|
this.storage.remove(this.name, true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// removes the storage entry and deletes all loaded data
|
|
|
|
|
// forced name for tests
|
|
|
|
|
MixpanelPersistence.prototype.clear = function() {
|
|
|
|
|
this.remove();
|
|
|
|
|
this['props'] = {};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {Object} props
|
|
|
|
|
* @param {*=} default_value
|
|
|
|
|
* @param {number=} days
|
|
|
|
|
*/
|
|
|
|
|
MixpanelPersistence.prototype.register_once = function(props, default_value, days) {
|
|
|
|
|
if (_.isObject(props)) {
|
|
|
|
|
if (typeof(default_value) === 'undefined') { default_value = 'None'; }
|
|
|
|
|
this.expire_days = (typeof(days) === 'undefined') ? this.default_expiry : days;
|
|
|
|
|
|
|
|
|
|
_.each(props, function(val, prop) {
|
|
|
|
|
if (!this['props'].hasOwnProperty(prop) || this['props'][prop] === default_value) {
|
|
|
|
|
this['props'][prop] = val;
|
|
|
|
|
}
|
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
|
|
this.save();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {Object} props
|
|
|
|
|
* @param {number=} days
|
|
|
|
|
*/
|
|
|
|
|
MixpanelPersistence.prototype.register = function(props, days) {
|
|
|
|
|
if (_.isObject(props)) {
|
|
|
|
|
this.expire_days = (typeof(days) === 'undefined') ? this.default_expiry : days;
|
|
|
|
|
|
|
|
|
|
_.extend(this['props'], props);
|
|
|
|
|
|
|
|
|
|
this.save();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype.unregister = function(prop) {
|
|
|
|
|
if (prop in this['props']) {
|
|
|
|
|
delete this['props'][prop];
|
|
|
|
|
this.save();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype._expire_notification_campaigns = _.safewrap(function() {
|
|
|
|
|
var campaigns_shown = this['props'][CAMPAIGN_IDS_KEY],
|
|
|
|
|
EXPIRY_TIME = Config.DEBUG ? 60 * 1000 : 60 * 60 * 1000; // 1 minute (Config.DEBUG) / 1 hour (PDXN)
|
|
|
|
|
if (!campaigns_shown) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
for (var campaign_id in campaigns_shown) {
|
|
|
|
|
if (1 * new Date() - campaigns_shown[campaign_id] > EXPIRY_TIME) {
|
|
|
|
|
delete campaigns_shown[campaign_id];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (_.isEmptyObject(campaigns_shown)) {
|
|
|
|
|
delete this['props'][CAMPAIGN_IDS_KEY];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype.update_campaign_params = function() {
|
|
|
|
|
if (!this.campaign_params_saved) {
|
|
|
|
|
this.register_once(_.info.campaignParams());
|
|
|
|
|
this.campaign_params_saved = true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype.update_search_keyword = function(referrer) {
|
|
|
|
|
this.register(_.info.searchInfo(referrer));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// EXPORTED METHOD, we test this directly.
|
|
|
|
|
MixpanelPersistence.prototype.update_referrer_info = function(referrer) {
|
|
|
|
|
// If referrer doesn't exist, we want to note the fact that it was type-in traffic.
|
|
|
|
|
this.register_once({
|
|
|
|
|
'$initial_referrer': referrer || '$direct',
|
|
|
|
|
'$initial_referring_domain': _.info.referringDomain(referrer) || '$direct'
|
|
|
|
|
}, '');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype.get_referrer_info = function() {
|
|
|
|
|
return _.strip_empty_properties({
|
|
|
|
|
'$initial_referrer': this['props']['$initial_referrer'],
|
|
|
|
|
'$initial_referring_domain': this['props']['$initial_referring_domain']
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// safely fills the passed in object with stored properties,
|
|
|
|
|
// does not override any properties defined in both
|
|
|
|
|
// returns the passed in object
|
|
|
|
|
MixpanelPersistence.prototype.safe_merge = function(props) {
|
|
|
|
|
_.each(this['props'], function(val, prop) {
|
|
|
|
|
if (!(prop in props)) {
|
|
|
|
|
props[prop] = val;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return props;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype.update_config = function(config) {
|
|
|
|
|
this.default_expiry = this.expire_days = config['cookie_expiration'];
|
|
|
|
|
this.set_disabled(config['disable_persistence']);
|
|
|
|
|
this.set_cross_subdomain(config['cross_subdomain_cookie']);
|
|
|
|
|
this.set_secure(config['secure_cookie']);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype.set_disabled = function(disabled) {
|
|
|
|
|
this.disabled = disabled;
|
|
|
|
|
if (this.disabled) {
|
|
|
|
|
this.remove();
|
|
|
|
|
} else {
|
|
|
|
|
this.save();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype.set_cross_subdomain = function(cross_subdomain) {
|
|
|
|
|
if (cross_subdomain !== this.cross_subdomain) {
|
|
|
|
|
this.cross_subdomain = cross_subdomain;
|
|
|
|
|
this.remove();
|
|
|
|
|
this.save();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype.get_cross_subdomain = function() {
|
|
|
|
|
return this.cross_subdomain;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype.set_secure = function(secure) {
|
|
|
|
|
if (secure !== this.secure) {
|
|
|
|
|
this.secure = secure ? true : false;
|
|
|
|
|
this.remove();
|
|
|
|
|
this.save();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype._add_to_people_queue = function(queue, data) {
|
|
|
|
|
var q_key = this._get_queue_key(queue),
|
|
|
|
|
q_data = data[queue],
|
|
|
|
|
set_q = this._get_or_create_queue(SET_ACTION),
|
|
|
|
|
set_once_q = this._get_or_create_queue(SET_ONCE_ACTION),
|
|
|
|
|
unset_q = this._get_or_create_queue(UNSET_ACTION),
|
|
|
|
|
add_q = this._get_or_create_queue(ADD_ACTION),
|
|
|
|
|
union_q = this._get_or_create_queue(UNION_ACTION),
|
|
|
|
|
append_q = this._get_or_create_queue(APPEND_ACTION, []);
|
|
|
|
|
|
|
|
|
|
if (q_key === SET_QUEUE_KEY) {
|
|
|
|
|
// Update the set queue - we can override any existing values
|
|
|
|
|
_.extend(set_q, q_data);
|
|
|
|
|
// if there was a pending increment, override it
|
|
|
|
|
// with the set.
|
|
|
|
|
this._pop_from_people_queue(ADD_ACTION, q_data);
|
|
|
|
|
// if there was a pending union, override it
|
|
|
|
|
// with the set.
|
|
|
|
|
this._pop_from_people_queue(UNION_ACTION, q_data);
|
|
|
|
|
this._pop_from_people_queue(UNSET_ACTION, q_data);
|
|
|
|
|
} else if (q_key === SET_ONCE_QUEUE_KEY) {
|
|
|
|
|
// only queue the data if there is not already a set_once call for it.
|
|
|
|
|
_.each(q_data, function(v, k) {
|
|
|
|
|
if (!(k in set_once_q)) {
|
|
|
|
|
set_once_q[k] = v;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this._pop_from_people_queue(UNSET_ACTION, q_data);
|
|
|
|
|
} else if (q_key === UNSET_QUEUE_KEY) {
|
|
|
|
|
_.each(q_data, function(prop) {
|
|
|
|
|
|
|
|
|
|
// undo previously-queued actions on this key
|
|
|
|
|
_.each([set_q, set_once_q, add_q, union_q], function(enqueued_obj) {
|
|
|
|
|
if (prop in enqueued_obj) {
|
|
|
|
|
delete enqueued_obj[prop];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
_.each(append_q, function(append_obj) {
|
|
|
|
|
if (prop in append_obj) {
|
|
|
|
|
delete append_obj[prop];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
unset_q[prop] = true;
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
} else if (q_key === ADD_QUEUE_KEY) {
|
|
|
|
|
_.each(q_data, function(v, k) {
|
|
|
|
|
// If it exists in the set queue, increment
|
|
|
|
|
// the value
|
|
|
|
|
if (k in set_q) {
|
|
|
|
|
set_q[k] += v;
|
|
|
|
|
} else {
|
|
|
|
|
// If it doesn't exist, update the add
|
|
|
|
|
// queue
|
|
|
|
|
if (!(k in add_q)) {
|
|
|
|
|
add_q[k] = 0;
|
|
|
|
|
}
|
|
|
|
|
add_q[k] += v;
|
|
|
|
|
}
|
|
|
|
|
}, this);
|
|
|
|
|
this._pop_from_people_queue(UNSET_ACTION, q_data);
|
|
|
|
|
} else if (q_key === UNION_QUEUE_KEY) {
|
|
|
|
|
_.each(q_data, function(v, k) {
|
|
|
|
|
if (_.isArray(v)) {
|
|
|
|
|
if (!(k in union_q)) {
|
|
|
|
|
union_q[k] = [];
|
|
|
|
|
}
|
|
|
|
|
// We may send duplicates, the server will dedup them.
|
|
|
|
|
union_q[k] = union_q[k].concat(v);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this._pop_from_people_queue(UNSET_ACTION, q_data);
|
|
|
|
|
} else if (q_key === APPEND_QUEUE_KEY) {
|
|
|
|
|
append_q.push(q_data);
|
|
|
|
|
this._pop_from_people_queue(UNSET_ACTION, q_data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console$1.log('MIXPANEL PEOPLE REQUEST (QUEUED, PENDING IDENTIFY):');
|
|
|
|
|
console$1.log(data);
|
|
|
|
|
|
|
|
|
|
this.save();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype._pop_from_people_queue = function(queue, data) {
|
|
|
|
|
var q = this._get_queue(queue);
|
|
|
|
|
if (!_.isUndefined(q)) {
|
|
|
|
|
_.each(data, function(v, k) {
|
|
|
|
|
delete q[k];
|
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
|
|
this.save();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype._get_queue_key = function(queue) {
|
|
|
|
|
if (queue === SET_ACTION) {
|
|
|
|
|
return SET_QUEUE_KEY;
|
|
|
|
|
} else if (queue === SET_ONCE_ACTION) {
|
|
|
|
|
return SET_ONCE_QUEUE_KEY;
|
|
|
|
|
} else if (queue === UNSET_ACTION) {
|
|
|
|
|
return UNSET_QUEUE_KEY;
|
|
|
|
|
} else if (queue === ADD_ACTION) {
|
|
|
|
|
return ADD_QUEUE_KEY;
|
|
|
|
|
} else if (queue === APPEND_ACTION) {
|
|
|
|
|
return APPEND_QUEUE_KEY;
|
|
|
|
|
} else if (queue === UNION_ACTION) {
|
|
|
|
|
return UNION_QUEUE_KEY;
|
|
|
|
|
} else {
|
|
|
|
|
console$1.error('Invalid queue:', queue);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype._get_queue = function(queue) {
|
|
|
|
|
return this['props'][this._get_queue_key(queue)];
|
|
|
|
|
};
|
|
|
|
|
MixpanelPersistence.prototype._get_or_create_queue = function(queue, default_val) {
|
|
|
|
|
var key = this._get_queue_key(queue);
|
|
|
|
|
default_val = _.isUndefined(default_val) ? {} : default_val;
|
|
|
|
|
|
|
|
|
|
return this['props'][key] || (this['props'][key] = default_val);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype.set_event_timer = function(event_name, timestamp) {
|
|
|
|
|
var timers = this['props'][EVENT_TIMERS_KEY] || {};
|
|
|
|
|
timers[event_name] = timestamp;
|
|
|
|
|
this['props'][EVENT_TIMERS_KEY] = timers;
|
|
|
|
|
this.save();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPersistence.prototype.remove_event_timer = function(event_name) {
|
|
|
|
|
var timers = this['props'][EVENT_TIMERS_KEY] || {};
|
|
|
|
|
var timestamp = timers[event_name];
|
|
|
|
|
if (!_.isUndefined(timestamp)) {
|
|
|
|
|
delete this['props'][EVENT_TIMERS_KEY][event_name];
|
|
|
|
|
this.save();
|
|
|
|
|
}
|
|
|
|
|
return timestamp;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Mixpanel Library Object
|
|
|
|
|
* @constructor
|
|
|
|
|
*/
|
|
|
|
|
var MixpanelLib = function() {};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Mixpanel People Object
|
|
|
|
|
* @constructor
|
|
|
|
|
*/
|
|
|
|
|
var MixpanelPeople = function() {};
|
|
|
|
|
|
|
|
|
|
var MPNotif;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* create_mplib(token:string, config:object, name:string)
|
|
|
|
|
*
|
|
|
|
|
* This function is used by the init method of MixpanelLib objects
|
|
|
|
|
* as well as the main initializer at the end of the JSLib (that
|
|
|
|
|
* initializes document.mixpanel as well as any additional instances
|
|
|
|
|
* declared before this file has loaded).
|
|
|
|
|
*/
|
|
|
|
|
var create_mplib = function(token, config, name) {
|
|
|
|
|
var instance,
|
|
|
|
|
target = (name === PRIMARY_INSTANCE_NAME) ? mixpanel_master : mixpanel_master[name];
|
|
|
|
|
|
|
|
|
|
if (target && init_type === INIT_MODULE) {
|
|
|
|
|
instance = target;
|
|
|
|
|
} else {
|
|
|
|
|
if (target && !_.isArray(target)) {
|
|
|
|
|
console$1.error('You have already initialized ' + name);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
instance = new MixpanelLib();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
instance._init(token, config, name);
|
|
|
|
|
|
|
|
|
|
instance['people'] = new MixpanelPeople();
|
|
|
|
|
instance['people']._init(instance);
|
|
|
|
|
|
|
|
|
|
// if any instance on the page has debug = true, we set the
|
|
|
|
|
// global debug to be true
|
|
|
|
|
Config.DEBUG = Config.DEBUG || instance.get_config('debug');
|
|
|
|
|
|
|
|
|
|
instance['__autotrack_enabled'] = instance.get_config('autotrack');
|
|
|
|
|
if (instance.get_config('autotrack')) {
|
|
|
|
|
var num_buckets = 100;
|
|
|
|
|
var num_enabled_buckets = 100;
|
|
|
|
|
if (!autotrack.enabledForProject(instance.get_config('token'), num_buckets, num_enabled_buckets)) {
|
|
|
|
|
instance['__autotrack_enabled'] = false;
|
|
|
|
|
console$1.log('Not in active bucket: disabling Automatic Event Collection.');
|
|
|
|
|
} else if (!autotrack.isBrowserSupported()) {
|
|
|
|
|
instance['__autotrack_enabled'] = false;
|
|
|
|
|
console$1.log('Disabling Automatic Event Collection because this browser is not supported');
|
|
|
|
|
} else {
|
|
|
|
|
autotrack.init(instance);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if target is not defined, we called init after the lib already
|
|
|
|
|
// loaded, so there won't be an array of things to execute
|
|
|
|
|
if (!_.isUndefined(target) && _.isArray(target)) {
|
|
|
|
|
// Crunch through the people queue first - we queue this data up &
|
|
|
|
|
// flush on identify, so it's better to do all these operations first
|
|
|
|
|
instance._execute_array.call(instance['people'], target['people']);
|
|
|
|
|
instance._execute_array(target);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return instance;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Initialization methods
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This function initializes a new instance of the Mixpanel tracking object.
|
|
|
|
|
* All new instances are added to the main mixpanel object as sub properties (such as
|
|
|
|
|
* mixpanel.library_name) and also returned by this function. To define a
|
|
|
|
|
* second instance on the page, you would call:
|
|
|
|
|
*
|
|
|
|
|
* mixpanel.init('new token', { your: 'config' }, 'library_name');
|
|
|
|
|
*
|
|
|
|
|
* and use it like so:
|
|
|
|
|
*
|
|
|
|
|
* mixpanel.library_name.track(...);
|
|
|
|
|
*
|
|
|
|
|
* @param {String} token Your Mixpanel API token
|
|
|
|
|
* @param {Object} [config] A dictionary of config options to override. <a href="https://github.com/mixpanel/mixpanel-js/blob/8b2e1f7b/src/mixpanel-core.js#L87-L110">See a list of default config options</a>.
|
|
|
|
|
* @param {String} [name] The name for the new mixpanel instance that you want created
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.init = function (token, config, name) {
|
|
|
|
|
if (_.isUndefined(name)) {
|
|
|
|
|
console$1.error('You must name your new library: init(token, config, name)');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (name === PRIMARY_INSTANCE_NAME) {
|
|
|
|
|
console$1.error('You must initialize the main mixpanel object right after you include the Mixpanel js snippet');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var instance = create_mplib(token, config, name);
|
|
|
|
|
mixpanel_master[name] = instance;
|
|
|
|
|
instance._loaded();
|
|
|
|
|
|
|
|
|
|
return instance;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// mixpanel._init(token:string, config:object, name:string)
|
|
|
|
|
//
|
|
|
|
|
// This function sets up the current instance of the mixpanel
|
|
|
|
|
// library. The difference between this method and the init(...)
|
|
|
|
|
// method is this one initializes the actual instance, whereas the
|
|
|
|
|
// init(...) method sets up a new library and calls _init on it.
|
|
|
|
|
//
|
|
|
|
|
MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
|
|
this['__loaded'] = true;
|
|
|
|
|
this['config'] = {};
|
|
|
|
|
|
|
|
|
|
this.set_config(_.extend({}, DEFAULT_CONFIG, config, {
|
|
|
|
|
'name': name,
|
|
|
|
|
'token': token,
|
|
|
|
|
'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
this['_jsc'] = function() {};
|
|
|
|
|
|
|
|
|
|
this.__dom_loaded_queue = [];
|
|
|
|
|
this.__request_queue = [];
|
|
|
|
|
this.__disabled_events = [];
|
|
|
|
|
this._flags = {
|
|
|
|
|
'disable_all_events': false,
|
|
|
|
|
'identify_called': false
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this['persistence'] = this['cookie'] = new MixpanelPersistence(this['config']);
|
|
|
|
|
this._init_gdpr_persistence();
|
|
|
|
|
|
|
|
|
|
this.register_once({'distinct_id': _.UUID()}, '');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Private methods
|
|
|
|
|
|
|
|
|
|
MixpanelLib.prototype._update_persistence = function() {
|
|
|
|
|
var disablePersistence = this.get_config('disable_persistence') || this.has_opted_out_tracking();
|
|
|
|
|
if (this['persistence'].disabled !== disablePersistence) {
|
|
|
|
|
this['persistence'].set_disabled(disablePersistence);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelLib.prototype._loaded = function() {
|
|
|
|
|
this.get_config('loaded')(this);
|
|
|
|
|
|
|
|
|
|
// this happens after so a user can call identify/name_tag in
|
|
|
|
|
// the loaded callback
|
|
|
|
|
if (this.get_config('track_pageview')) {
|
|
|
|
|
this.track_pageview();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelLib.prototype._dom_loaded = function() {
|
|
|
|
|
_.each(this.__dom_loaded_queue, function(item) {
|
|
|
|
|
this._track_dom.apply(this, item);
|
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
|
|
if (!this.has_opted_out_tracking()) {
|
|
|
|
|
_.each(this.__request_queue, function(item) {
|
|
|
|
|
this._send_request.apply(this, item);
|
|
|
|
|
}, this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delete this.__dom_loaded_queue;
|
|
|
|
|
delete this.__request_queue;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelLib.prototype._track_dom = function(DomClass, args) {
|
|
|
|
|
if (this.get_config('img')) {
|
|
|
|
|
console$1.error('You can\'t use DOM tracking functions with img = true.');
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!DOM_LOADED) {
|
|
|
|
|
this.__dom_loaded_queue.push([DomClass, args]);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var dt = new DomClass().init(this);
|
|
|
|
|
return dt.track.apply(dt, args);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* _prepare_callback() should be called by callers of _send_request for use
|
|
|
|
|
* as the callback argument.
|
|
|
|
|
*
|
|
|
|
|
* If there is no callback, this returns null.
|
|
|
|
|
* If we are going to make XHR/XDR requests, this returns a function.
|
|
|
|
|
* If we are going to use script tags, this returns a string to use as the
|
|
|
|
|
* callback GET param.
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype._prepare_callback = function(callback, data) {
|
|
|
|
|
if (_.isUndefined(callback)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (USE_XHR) {
|
|
|
|
|
var callback_function = function(response) {
|
|
|
|
|
callback(response, data);
|
|
|
|
|
};
|
|
|
|
|
return callback_function;
|
|
|
|
|
} else {
|
|
|
|
|
// if the user gives us a callback, we store as a random
|
|
|
|
|
// property on this instances jsc function and update our
|
|
|
|
|
// callback string to reflect that.
|
|
|
|
|
var jsc = this['_jsc'];
|
|
|
|
|
var randomized_cb = '' + Math.floor(Math.random() * 100000000);
|
|
|
|
|
var callback_string = this.get_config('callback_fn') + '[' + randomized_cb + ']';
|
|
|
|
|
jsc[randomized_cb] = function(response) {
|
|
|
|
|
delete jsc[randomized_cb];
|
|
|
|
|
callback(response, data);
|
|
|
|
|
};
|
|
|
|
|
return callback_string;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelLib.prototype._send_request = function(url, data, callback) {
|
|
|
|
|
if (ENQUEUE_REQUESTS) {
|
|
|
|
|
this.__request_queue.push(arguments);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// needed to correctly format responses
|
|
|
|
|
var verbose_mode = this.get_config('verbose');
|
|
|
|
|
if (data['verbose']) { verbose_mode = true; }
|
|
|
|
|
|
|
|
|
|
if (this.get_config('test')) { data['test'] = 1; }
|
|
|
|
|
if (verbose_mode) { data['verbose'] = 1; }
|
|
|
|
|
if (this.get_config('img')) { data['img'] = 1; }
|
|
|
|
|
if (!USE_XHR) {
|
|
|
|
|
if (callback) {
|
|
|
|
|
data['callback'] = callback;
|
|
|
|
|
} else if (verbose_mode || this.get_config('test')) {
|
|
|
|
|
// Verbose output (from verbose mode, or an error in test mode) is a json blob,
|
|
|
|
|
// which by itself is not valid javascript. Without a callback, this verbose output will
|
|
|
|
|
// cause an error when returned via jsonp, so we force a no-op callback param.
|
|
|
|
|
// See the ECMA script spec: http://www.ecma-international.org/ecma-262/5.1/#sec-12.4
|
|
|
|
|
data['callback'] = '(function(){})';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data['ip'] = this.get_config('ip')?1:0;
|
|
|
|
|
data['_'] = new Date().getTime().toString();
|
|
|
|
|
url += '?' + _.HTTPBuildQuery(data);
|
|
|
|
|
|
|
|
|
|
if ('img' in data) {
|
|
|
|
|
var img = document$1.createElement('img');
|
|
|
|
|
img.src = url;
|
|
|
|
|
document$1.body.appendChild(img);
|
|
|
|
|
} else if (USE_XHR) {
|
|
|
|
|
try {
|
|
|
|
|
var req = new XMLHttpRequest();
|
|
|
|
|
req.open('GET', url, true);
|
|
|
|
|
|
|
|
|
|
var headers = this.get_config('xhr_headers');
|
|
|
|
|
_.each(headers, function(headerValue, headerName) {
|
|
|
|
|
req.setRequestHeader(headerName, headerValue);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// send the mp_optout cookie
|
|
|
|
|
// withCredentials cannot be modified until after calling .open on Android and Mobile Safari
|
|
|
|
|
req.withCredentials = true;
|
|
|
|
|
req.onreadystatechange = function () {
|
|
|
|
|
if (req.readyState === 4) { // XMLHttpRequest.DONE == 4, except in safari 4
|
|
|
|
|
if (req.status === 200) {
|
|
|
|
|
if (callback) {
|
|
|
|
|
if (verbose_mode) {
|
|
|
|
|
var response;
|
|
|
|
|
try {
|
|
|
|
|
response = _.JSONDecode(req.responseText);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console$1.error(e);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
callback(response);
|
|
|
|
|
} else {
|
|
|
|
|
callback(Number(req.responseText));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
var error = 'Bad HTTP status: ' + req.status + ' ' + req.statusText;
|
|
|
|
|
console$1.error(error);
|
|
|
|
|
if (callback) {
|
|
|
|
|
if (verbose_mode) {
|
|
|
|
|
callback({status: 0, error: error});
|
|
|
|
|
} else {
|
|
|
|
|
callback(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
req.send(null);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console$1.error(e);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
var script = document$1.createElement('script');
|
|
|
|
|
script.type = 'text/javascript';
|
|
|
|
|
script.async = true;
|
|
|
|
|
script.defer = true;
|
|
|
|
|
script.src = url;
|
|
|
|
|
var s = document$1.getElementsByTagName('script')[0];
|
|
|
|
|
s.parentNode.insertBefore(script, s);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* _execute_array() deals with processing any mixpanel function
|
|
|
|
|
* calls that were called before the Mixpanel library were loaded
|
|
|
|
|
* (and are thus stored in an array so they can be called later)
|
|
|
|
|
*
|
|
|
|
|
* Note: we fire off all the mixpanel function calls && user defined
|
|
|
|
|
* functions BEFORE we fire off mixpanel tracking calls. This is so
|
|
|
|
|
* identify/register/set_config calls can properly modify early
|
|
|
|
|
* tracking calls.
|
|
|
|
|
*
|
|
|
|
|
* @param {Array} array
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype._execute_array = function(array) {
|
|
|
|
|
var fn_name, alias_calls = [], other_calls = [], tracking_calls = [];
|
|
|
|
|
_.each(array, function(item) {
|
|
|
|
|
if (item) {
|
|
|
|
|
fn_name = item[0];
|
|
|
|
|
if (typeof(item) === 'function') {
|
|
|
|
|
item.call(this);
|
|
|
|
|
} else if (_.isArray(item) && fn_name === 'alias') {
|
|
|
|
|
alias_calls.push(item);
|
|
|
|
|
} else if (_.isArray(item) && fn_name.indexOf('track') !== -1 && typeof(this[fn_name]) === 'function') {
|
|
|
|
|
tracking_calls.push(item);
|
|
|
|
|
} else {
|
|
|
|
|
other_calls.push(item);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
|
|
var execute = function(calls, context) {
|
|
|
|
|
_.each(calls, function(item) {
|
|
|
|
|
this[item[0]].apply(this, item.slice(1));
|
|
|
|
|
}, context);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
execute(alias_calls, this);
|
|
|
|
|
execute(other_calls, this);
|
|
|
|
|
execute(tracking_calls, this);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* push() keeps the standard async-array-push
|
|
|
|
|
* behavior around after the lib is loaded.
|
|
|
|
|
* This is only useful for external integrations that
|
|
|
|
|
* do not wish to rely on our convenience methods
|
|
|
|
|
* (created in the snippet).
|
|
|
|
|
*
|
|
|
|
|
* ### Usage:
|
|
|
|
|
* mixpanel.push(['register', { a: 'b' }]);
|
|
|
|
|
*
|
|
|
|
|
* @param {Array} item A [function_name, args...] array to be executed
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.push = function(item) {
|
|
|
|
|
this._execute_array([item]);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Disable events on the Mixpanel object. If passed no arguments,
|
|
|
|
|
* this function disables tracking of any event. If passed an
|
|
|
|
|
* array of event names, those events will be disabled, but other
|
|
|
|
|
* events will continue to be tracked.
|
|
|
|
|
*
|
|
|
|
|
* Note: this function does not stop other mixpanel functions from
|
|
|
|
|
* firing, such as register() or people.set().
|
|
|
|
|
*
|
|
|
|
|
* @param {Array} [events] An array of event names to disable
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.disable = function(events) {
|
|
|
|
|
if (typeof(events) === 'undefined') {
|
|
|
|
|
this._flags.disable_all_events = true;
|
|
|
|
|
} else {
|
|
|
|
|
this.__disabled_events = this.__disabled_events.concat(events);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Track an event. This is the most important and
|
|
|
|
|
* frequently used Mixpanel function.
|
|
|
|
|
*
|
|
|
|
|
* ### Usage:
|
|
|
|
|
*
|
|
|
|
|
* // track an event named 'Registered'
|
|
|
|
|
* mixpanel.track('Registered', {'Gender': 'Male', 'Age': 21});
|
|
|
|
|
*
|
|
|
|
|
* To track link clicks or form submissions, see track_links() or track_forms().
|
|
|
|
|
*
|
|
|
|
|
* @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc.
|
|
|
|
|
* @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself.
|
|
|
|
|
* @param {Function} [callback] If provided, the callback function will be called after tracking the event.
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, properties, callback) {
|
|
|
|
|
if (typeof(callback) !== 'function') {
|
|
|
|
|
callback = function() {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_.isUndefined(event_name)) {
|
|
|
|
|
console$1.error('No event name provided to mixpanel.track');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this._event_is_disabled(event_name)) {
|
|
|
|
|
callback(0);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// set defaults
|
|
|
|
|
properties = properties || {};
|
|
|
|
|
properties['token'] = this.get_config('token');
|
|
|
|
|
|
|
|
|
|
// set $duration if time_event was previously called for this event
|
|
|
|
|
var start_timestamp = this['persistence'].remove_event_timer(event_name);
|
|
|
|
|
if (!_.isUndefined(start_timestamp)) {
|
|
|
|
|
var duration_in_ms = new Date().getTime() - start_timestamp;
|
|
|
|
|
properties['$duration'] = parseFloat((duration_in_ms / 1000).toFixed(3));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// update persistence
|
|
|
|
|
this['persistence'].update_search_keyword(document$1.referrer);
|
|
|
|
|
|
|
|
|
|
if (this.get_config('store_google')) { this['persistence'].update_campaign_params(); }
|
|
|
|
|
if (this.get_config('save_referrer')) { this['persistence'].update_referrer_info(document$1.referrer); }
|
|
|
|
|
|
|
|
|
|
// note: extend writes to the first object, so lets make sure we
|
|
|
|
|
// don't write to the persistence properties object and info
|
|
|
|
|
// properties object by passing in a new object
|
|
|
|
|
|
|
|
|
|
// update properties with pageview info and super-properties
|
|
|
|
|
properties = _.extend(
|
|
|
|
|
{},
|
|
|
|
|
_.info.properties(),
|
|
|
|
|
this['persistence'].properties(),
|
|
|
|
|
properties
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var property_blacklist = this.get_config('property_blacklist');
|
|
|
|
|
if (_.isArray(property_blacklist)) {
|
|
|
|
|
_.each(property_blacklist, function(blacklisted_prop) {
|
|
|
|
|
delete properties[blacklisted_prop];
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
console$1.error('Invalid value for property_blacklist config: ' + property_blacklist);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var data = {
|
|
|
|
|
'event': event_name,
|
|
|
|
|
'properties': properties
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var truncated_data = _.truncate(data, 255);
|
|
|
|
|
var json_data = _.JSONEncode(truncated_data);
|
|
|
|
|
var encoded_data = _.base64Encode(json_data);
|
|
|
|
|
|
|
|
|
|
console$1.log('MIXPANEL REQUEST:');
|
|
|
|
|
console$1.log(truncated_data);
|
|
|
|
|
|
|
|
|
|
this._send_request(
|
|
|
|
|
this.get_config('api_host') + '/track/',
|
|
|
|
|
{ 'data': encoded_data },
|
|
|
|
|
this._prepare_callback(callback, truncated_data)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return truncated_data;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Track a page view event, which is currently ignored by the server.
|
|
|
|
|
* This function is called by default on page load unless the
|
|
|
|
|
* track_pageview configuration variable is false.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} [page] The url of the page to record. If you don't include this, it defaults to the current url.
|
|
|
|
|
* @api private
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.track_pageview = function(page) {
|
|
|
|
|
if (_.isUndefined(page)) {
|
|
|
|
|
page = document$1.location.href;
|
|
|
|
|
}
|
|
|
|
|
this.track('mp_page_view', _.info.pageviewInfo(page));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Track clicks on a set of document elements. Selector must be a
|
|
|
|
|
* valid query. Elements must exist on the page at the time track_links is called.
|
|
|
|
|
*
|
|
|
|
|
* ### Usage:
|
|
|
|
|
*
|
|
|
|
|
* // track click for link id #nav
|
|
|
|
|
* mixpanel.track_links('#nav', 'Clicked Nav Link');
|
|
|
|
|
*
|
|
|
|
|
* ### Notes:
|
|
|
|
|
*
|
|
|
|
|
* This function will wait up to 300 ms for the Mixpanel
|
|
|
|
|
* servers to respond. If they have not responded by that time
|
|
|
|
|
* it will head to the link without ensuring that your event
|
|
|
|
|
* has been tracked. To configure this timeout please see the
|
|
|
|
|
* set_config() documentation below.
|
|
|
|
|
*
|
|
|
|
|
* If you pass a function in as the properties argument, the
|
|
|
|
|
* function will receive the DOMElement that triggered the
|
|
|
|
|
* event as an argument. You are expected to return an object
|
|
|
|
|
* from the function; any properties defined on this object
|
|
|
|
|
* will be sent to mixpanel as event properties.
|
|
|
|
|
*
|
|
|
|
|
* @type {Function}
|
|
|
|
|
* @param {Object|String} query A valid DOM query, element or jQuery-esque list
|
|
|
|
|
* @param {String} event_name The name of the event to track
|
|
|
|
|
* @param {Object|Function} [properties] A properties object or function that returns a dictionary of properties when passed a DOMElement
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.track_links = function() {
|
|
|
|
|
return this._track_dom.call(this, LinkTracker, arguments);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Track form submissions. Selector must be a valid query.
|
|
|
|
|
*
|
|
|
|
|
* ### Usage:
|
|
|
|
|
*
|
|
|
|
|
* // track submission for form id 'register'
|
|
|
|
|
* mixpanel.track_forms('#register', 'Created Account');
|
|
|
|
|
*
|
|
|
|
|
* ### Notes:
|
|
|
|
|
*
|
|
|
|
|
* This function will wait up to 300 ms for the mixpanel
|
|
|
|
|
* servers to respond, if they have not responded by that time
|
|
|
|
|
* it will head to the link without ensuring that your event
|
|
|
|
|
* has been tracked. To configure this timeout please see the
|
|
|
|
|
* set_config() documentation below.
|
|
|
|
|
*
|
|
|
|
|
* If you pass a function in as the properties argument, the
|
|
|
|
|
* function will receive the DOMElement that triggered the
|
|
|
|
|
* event as an argument. You are expected to return an object
|
|
|
|
|
* from the function; any properties defined on this object
|
|
|
|
|
* will be sent to mixpanel as event properties.
|
|
|
|
|
*
|
|
|
|
|
* @type {Function}
|
|
|
|
|
* @param {Object|String} query A valid DOM query, element or jQuery-esque list
|
|
|
|
|
* @param {String} event_name The name of the event to track
|
|
|
|
|
* @param {Object|Function} [properties] This can be a set of properties, or a function that returns a set of properties after being passed a DOMElement
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.track_forms = function() {
|
|
|
|
|
return this._track_dom.call(this, FormTracker, arguments);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Time an event by including the time between this call and a
|
|
|
|
|
* later 'track' call for the same event in the properties sent
|
|
|
|
|
* with the event.
|
|
|
|
|
*
|
|
|
|
|
* ### Usage:
|
|
|
|
|
*
|
|
|
|
|
* // time an event named 'Registered'
|
|
|
|
|
* mixpanel.time_event('Registered');
|
|
|
|
|
* mixpanel.track('Registered', {'Gender': 'Male', 'Age': 21});
|
|
|
|
|
*
|
|
|
|
|
* When called for a particular event name, the next track call for that event
|
|
|
|
|
* name will include the elapsed time between the 'time_event' and 'track'
|
|
|
|
|
* calls. This value is stored as seconds in the '$duration' property.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} event_name The name of the event.
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.time_event = function(event_name) {
|
|
|
|
|
if (_.isUndefined(event_name)) {
|
|
|
|
|
console$1.error('No event name provided to mixpanel.time_event');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this._event_is_disabled(event_name)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this['persistence'].set_event_timer(event_name, new Date().getTime());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Register a set of super properties, which are included with all
|
|
|
|
|
* events. This will overwrite previous super property values.
|
|
|
|
|
*
|
|
|
|
|
* ### Usage:
|
|
|
|
|
*
|
|
|
|
|
* // register 'Gender' as a super property
|
|
|
|
|
* mixpanel.register({'Gender': 'Female'});
|
|
|
|
|
*
|
|
|
|
|
* // register several super properties when a user signs up
|
|
|
|
|
* mixpanel.register({
|
|
|
|
|
* 'Email': 'jdoe@example.com',
|
|
|
|
|
* 'Account Type': 'Free'
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} properties An associative array of properties to store about the user
|
|
|
|
|
* @param {Number} [days] How many days since the user's last visit to store the super properties
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.register = function(props, days) {
|
|
|
|
|
this['persistence'].register(props, days);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Register a set of super properties only once. This will not
|
|
|
|
|
* overwrite previous super property values, unlike register().
|
|
|
|
|
*
|
|
|
|
|
* ### Usage:
|
|
|
|
|
*
|
|
|
|
|
* // register a super property for the first time only
|
|
|
|
|
* mixpanel.register_once({
|
|
|
|
|
* 'First Login Date': new Date().toISOString()
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* ### Notes:
|
|
|
|
|
*
|
|
|
|
|
* If default_value is specified, current super properties
|
|
|
|
|
* with that value will be overwritten.
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} properties An associative array of properties to store about the user
|
|
|
|
|
* @param {*} [default_value] Value to override if already set in super properties (ex: 'False') Default: 'None'
|
|
|
|
|
* @param {Number} [days] How many days since the users last visit to store the super properties
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.register_once = function(props, default_value, days) {
|
|
|
|
|
this['persistence'].register_once(props, default_value, days);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Delete a super property stored with the current user.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} property The name of the super property to remove
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.unregister = function(property) {
|
|
|
|
|
this['persistence'].unregister(property);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelLib.prototype._register_single = function(prop, value) {
|
|
|
|
|
var props = {};
|
|
|
|
|
props[prop] = value;
|
|
|
|
|
this.register(props);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Identify a user with a unique ID instead of a Mixpanel
|
|
|
|
|
* randomly generated distinct_id. If the method is never called,
|
|
|
|
|
* then unique visitors will be identified by a UUID generated
|
|
|
|
|
* the first time they visit the site.
|
|
|
|
|
*
|
|
|
|
|
* ### Notes:
|
|
|
|
|
*
|
|
|
|
|
* You can call this function to overwrite a previously set
|
|
|
|
|
* unique ID for the current user. Mixpanel cannot translate
|
|
|
|
|
* between IDs at this time, so when you change a user's ID
|
|
|
|
|
* they will appear to be a new user.
|
|
|
|
|
*
|
|
|
|
|
* When used alone, mixpanel.identify will change the user's
|
|
|
|
|
* distinct_id to the unique ID provided. When used in tandem
|
|
|
|
|
* with mixpanel.alias, it will allow you to identify based on
|
|
|
|
|
* unique ID and map that back to the original, anonymous
|
|
|
|
|
* distinct_id given to the user upon her first arrival to your
|
|
|
|
|
* site (thus connecting anonymous pre-signup activity to
|
|
|
|
|
* post-signup activity). Though the two work together, do not
|
|
|
|
|
* call identify() at the same time as alias(). Calling the two
|
|
|
|
|
* at the same time can cause a race condition, so it is best
|
|
|
|
|
* practice to call identify on the original, anonymous ID
|
|
|
|
|
* right after you've aliased it.
|
|
|
|
|
* <a href="https://mixpanel.com/help/questions/articles/how-should-i-handle-my-user-identity-with-the-mixpanel-javascript-library">Learn more about how mixpanel.identify and mixpanel.alias can be used</a>.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} [unique_id] A string that uniquely identifies a user. If not provided, the distinct_id currently in the persistent store (cookie or localStorage) will be used.
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.identify = function(
|
|
|
|
|
unique_id, _set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback
|
|
|
|
|
) {
|
|
|
|
|
// Optional Parameters
|
|
|
|
|
// _set_callback:function A callback to be run if and when the People set queue is flushed
|
|
|
|
|
// _add_callback:function A callback to be run if and when the People add queue is flushed
|
|
|
|
|
// _append_callback:function A callback to be run if and when the People append queue is flushed
|
|
|
|
|
// _set_once_callback:function A callback to be run if and when the People set_once queue is flushed
|
|
|
|
|
// _union_callback:function A callback to be run if and when the People union queue is flushed
|
|
|
|
|
// _unset_callback:function A callback to be run if and when the People unset queue is flushed
|
|
|
|
|
|
|
|
|
|
// identify only changes the distinct id if it doesn't match either the existing or the alias;
|
|
|
|
|
// if it's new, blow away the alias as well.
|
|
|
|
|
if (unique_id !== this.get_distinct_id() && unique_id !== this.get_property(ALIAS_ID_KEY)) {
|
|
|
|
|
this.unregister(ALIAS_ID_KEY);
|
|
|
|
|
this._register_single('distinct_id', unique_id);
|
|
|
|
|
}
|
|
|
|
|
this._check_and_handle_notifications(this.get_distinct_id());
|
|
|
|
|
this._flags.identify_called = true;
|
|
|
|
|
// Flush any queued up people requests
|
|
|
|
|
this['people']._flush(_set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clears super properties and generates a new random distinct_id for this instance.
|
|
|
|
|
* Useful for clearing data when a user logs out.
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.reset = function() {
|
|
|
|
|
this['persistence'].clear();
|
|
|
|
|
this._flags.identify_called = false;
|
|
|
|
|
this.register_once({'distinct_id': _.UUID()}, '');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the current distinct id of the user. This is either the id automatically
|
|
|
|
|
* generated by the library or the id that has been passed by a call to identify().
|
|
|
|
|
*
|
|
|
|
|
* ### Notes:
|
|
|
|
|
*
|
|
|
|
|
* get_distinct_id() can only be called after the Mixpanel library has finished loading.
|
|
|
|
|
* init() has a loaded function available to handle this automatically. For example:
|
|
|
|
|
*
|
|
|
|
|
* // set distinct_id after the mixpanel library has loaded
|
|
|
|
|
* mixpanel.init('YOUR PROJECT TOKEN', {
|
|
|
|
|
* loaded: function(mixpanel) {
|
|
|
|
|
* distinct_id = mixpanel.get_distinct_id();
|
|
|
|
|
* }
|
|
|
|
|
* });
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.get_distinct_id = function() {
|
|
|
|
|
return this.get_property('distinct_id');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create an alias, which Mixpanel will use to link two distinct_ids going forward (not retroactively).
|
|
|
|
|
* Multiple aliases can map to the same original ID, but not vice-versa. Aliases can also be chained - the
|
|
|
|
|
* following is a valid scenario:
|
|
|
|
|
*
|
|
|
|
|
* mixpanel.alias('new_id', 'existing_id');
|
|
|
|
|
* ...
|
|
|
|
|
* mixpanel.alias('newer_id', 'new_id');
|
|
|
|
|
*
|
|
|
|
|
* If the original ID is not passed in, we will use the current distinct_id - probably the auto-generated GUID.
|
|
|
|
|
*
|
|
|
|
|
* ### Notes:
|
|
|
|
|
*
|
|
|
|
|
* The best practice is to call alias() when a unique ID is first created for a user
|
|
|
|
|
* (e.g., when a user first registers for an account and provides an email address).
|
|
|
|
|
* alias() should never be called more than once for a given user, except to
|
|
|
|
|
* chain a newer ID to a previously new ID, as described above.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} alias A unique identifier that you want to use for this user in the future.
|
|
|
|
|
* @param {String} [original] The current identifier being used for this user.
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.alias = function(alias, original) {
|
|
|
|
|
// If the $people_distinct_id key exists in persistence, there has been a previous
|
|
|
|
|
// mixpanel.people.identify() call made for this user. It is VERY BAD to make an alias with
|
|
|
|
|
// this ID, as it will duplicate users.
|
|
|
|
|
if (alias === this.get_property(PEOPLE_DISTINCT_ID_KEY)) {
|
|
|
|
|
console$1.critical('Attempting to create alias for existing People user - aborting.');
|
|
|
|
|
return -2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var _this = this;
|
|
|
|
|
if (_.isUndefined(original)) {
|
|
|
|
|
original = this.get_distinct_id();
|
|
|
|
|
}
|
|
|
|
|
if (alias !== original) {
|
|
|
|
|
this._register_single(ALIAS_ID_KEY, alias);
|
|
|
|
|
return this.track('$create_alias', { 'alias': alias, 'distinct_id': original }, function() {
|
|
|
|
|
// Flush the people queue
|
|
|
|
|
_this.identify(alias);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
console$1.error('alias matches current distinct_id - skipping api call.');
|
|
|
|
|
this.identify(alias);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Provide a string to recognize the user by. The string passed to
|
|
|
|
|
* this method will appear in the Mixpanel Streams product rather
|
|
|
|
|
* than an automatically generated name. Name tags do not have to
|
|
|
|
|
* be unique.
|
|
|
|
|
*
|
|
|
|
|
* This value will only be included in Streams data.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} name_tag A human readable name for the user
|
|
|
|
|
* @api private
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.name_tag = function(name_tag) {
|
|
|
|
|
this._register_single('mp_name_tag', name_tag);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update the configuration of a mixpanel library instance.
|
|
|
|
|
*
|
|
|
|
|
* The default config is:
|
|
|
|
|
*
|
|
|
|
|
* {
|
|
|
|
|
* // super properties cookie expiration (in days)
|
|
|
|
|
* cookie_expiration: 365
|
|
|
|
|
*
|
|
|
|
|
* // super properties span subdomains
|
|
|
|
|
* cross_subdomain_cookie: true
|
|
|
|
|
*
|
|
|
|
|
* // debug mode
|
|
|
|
|
* debug: false
|
|
|
|
|
*
|
|
|
|
|
* // if this is true, the mixpanel cookie or localStorage entry
|
|
|
|
|
* // will be deleted, and no user persistence will take place
|
|
|
|
|
* disable_persistence: false
|
|
|
|
|
*
|
|
|
|
|
* // if this is true, Mixpanel will automatically determine
|
|
|
|
|
* // City, Region and Country data using the IP address of
|
|
|
|
|
* //the client
|
|
|
|
|
* ip: true
|
|
|
|
|
*
|
|
|
|
|
* // opt users out of tracking by this Mixpanel instance by default
|
|
|
|
|
* opt_out_tracking_by_default: false
|
|
|
|
|
*
|
|
|
|
|
* // persistence mechanism used by opt-in/opt-out methods - cookie
|
|
|
|
|
* // or localStorage - falls back to cookie if localStorage is unavailable
|
|
|
|
|
* opt_out_tracking_persistence_type: 'localStorage'
|
|
|
|
|
*
|
|
|
|
|
* // customize the name of cookie/localStorage set by opt-in/opt-out methods
|
|
|
|
|
* opt_out_tracking_cookie_prefix: null
|
|
|
|
|
*
|
|
|
|
|
* // type of persistent store for super properties (cookie/
|
|
|
|
|
* // localStorage) if set to 'localStorage', any existing
|
|
|
|
|
* // mixpanel cookie value with the same persistence_name
|
|
|
|
|
* // will be transferred to localStorage and deleted
|
|
|
|
|
* persistence: 'cookie'
|
|
|
|
|
*
|
|
|
|
|
* // name for super properties persistent store
|
|
|
|
|
* persistence_name: ''
|
|
|
|
|
*
|
|
|
|
|
* // names of properties/superproperties which should never
|
|
|
|
|
* // be sent with track() calls
|
|
|
|
|
* property_blacklist: []
|
|
|
|
|
*
|
|
|
|
|
* // if this is true, mixpanel cookies will be marked as
|
|
|
|
|
* // secure, meaning they will only be transmitted over https
|
|
|
|
|
* secure_cookie: false
|
|
|
|
|
*
|
|
|
|
|
* // the amount of time track_links will
|
|
|
|
|
* // wait for Mixpanel's servers to respond
|
|
|
|
|
* track_links_timeout: 300
|
|
|
|
|
*
|
|
|
|
|
* // should we track a page view on page load
|
|
|
|
|
* track_pageview: true
|
|
|
|
|
*
|
|
|
|
|
* // if you set upgrade to be true, the library will check for
|
|
|
|
|
* // a cookie from our old js library and import super
|
|
|
|
|
* // properties from it, then the old cookie is deleted
|
|
|
|
|
* // The upgrade config option only works in the initialization,
|
|
|
|
|
* // so make sure you set it when you create the library.
|
|
|
|
|
* upgrade: false
|
|
|
|
|
*
|
|
|
|
|
* // extra HTTP request headers to set for each API request, in
|
|
|
|
|
* // the format {'Header-Name': value}
|
|
|
|
|
* xhr_headers: {}
|
|
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} config A dictionary of new configuration values to update
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.set_config = function(config) {
|
|
|
|
|
if (_.isObject(config)) {
|
|
|
|
|
_.extend(this['config'], config);
|
|
|
|
|
|
|
|
|
|
if (!this.get_config('persistence_name')) {
|
|
|
|
|
this['config']['persistence_name'] = this['config']['cookie_name'];
|
|
|
|
|
}
|
|
|
|
|
if (!this.get_config('disable_persistence')) {
|
|
|
|
|
this['config']['disable_persistence'] = this['config']['disable_cookie'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this['persistence']) {
|
|
|
|
|
this['persistence'].update_config(this['config']);
|
|
|
|
|
}
|
|
|
|
|
Config.DEBUG = Config.DEBUG || this.get_config('debug');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* returns the current config object for the library.
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.get_config = function(prop_name) {
|
|
|
|
|
return this['config'][prop_name];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the value of the super property named property_name. If no such
|
|
|
|
|
* property is set, get_property() will return the undefined value.
|
|
|
|
|
*
|
|
|
|
|
* ### Notes:
|
|
|
|
|
*
|
|
|
|
|
* get_property() can only be called after the Mixpanel library has finished loading.
|
|
|
|
|
* init() has a loaded function available to handle this automatically. For example:
|
|
|
|
|
*
|
|
|
|
|
* // grab value for 'user_id' after the mixpanel library has loaded
|
|
|
|
|
* mixpanel.init('YOUR PROJECT TOKEN', {
|
|
|
|
|
* loaded: function(mixpanel) {
|
|
|
|
|
* user_id = mixpanel.get_property('user_id');
|
|
|
|
|
* }
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* @param {String} property_name The name of the super property you want to retrieve
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.get_property = function(property_name) {
|
|
|
|
|
return this['persistence']['props'][property_name];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelLib.prototype.toString = function() {
|
|
|
|
|
var name = this.get_config('name');
|
|
|
|
|
if (name !== PRIMARY_INSTANCE_NAME) {
|
|
|
|
|
name = PRIMARY_INSTANCE_NAME + '.' + name;
|
|
|
|
|
}
|
|
|
|
|
return name;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelLib.prototype._event_is_disabled = function(event_name) {
|
|
|
|
|
return _.isBlockedUA(userAgent) ||
|
|
|
|
|
this._flags.disable_all_events ||
|
|
|
|
|
_.include(this.__disabled_events, event_name);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelLib.prototype._check_and_handle_notifications = addOptOutCheckMixpanelLib(function(distinct_id) {
|
|
|
|
|
if (
|
|
|
|
|
!distinct_id ||
|
|
|
|
|
this._flags.identify_called ||
|
|
|
|
|
this.get_config('disable_notifications')
|
|
|
|
|
) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console$1.log('MIXPANEL NOTIFICATION CHECK');
|
|
|
|
|
|
|
|
|
|
var data = {
|
|
|
|
|
'verbose': true,
|
|
|
|
|
'version': '2',
|
|
|
|
|
'lib': 'web',
|
|
|
|
|
'token': this.get_config('token'),
|
|
|
|
|
'distinct_id': distinct_id
|
|
|
|
|
};
|
|
|
|
|
var self = this;
|
|
|
|
|
this._send_request(
|
|
|
|
|
this.get_config('api_host') + '/decide/',
|
|
|
|
|
data,
|
|
|
|
|
this._prepare_callback(function(r) {
|
|
|
|
|
if (r['notifications'] && r['notifications'].length > 0) {
|
|
|
|
|
self._show_notification.call(self, r['notifications'][0]);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
MixpanelLib.prototype._show_notification = function(notification_data) {
|
|
|
|
|
var notification = new MPNotif(notification_data, this);
|
|
|
|
|
notification.show();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// perform some housekeeping around GDPR persistence of opt-in/out state
|
|
|
|
|
MixpanelLib.prototype._init_gdpr_persistence = function() {
|
|
|
|
|
var is_localStorage_requested = this.get_config('opt_out_tracking_persistence_type') === 'localStorage';
|
|
|
|
|
|
|
|
|
|
// try to convert opt-in/out cookies to localStorage if possible
|
|
|
|
|
if (is_localStorage_requested && _.localStorage.is_supported()) {
|
|
|
|
|
if (!this.has_opted_in_tracking() && this.has_opted_in_tracking({'persistence_type': 'cookie'})) {
|
|
|
|
|
this.opt_in_tracking();
|
|
|
|
|
}
|
|
|
|
|
if (!this.has_opted_out_tracking() && this.has_opted_out_tracking({'persistence_type': 'cookie'})) {
|
|
|
|
|
this.opt_out_tracking();
|
|
|
|
|
}
|
|
|
|
|
this.clear_opt_in_out_tracking({'persistence_type': 'cookie'});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// check whether we should opt out by default and update persistence accordingly
|
|
|
|
|
if (this.get_config('opt_out_tracking_by_default') || _.cookie.get('mp_optout')) {
|
|
|
|
|
_.cookie.remove('mp_optout');
|
|
|
|
|
this.opt_out_tracking();
|
|
|
|
|
}
|
|
|
|
|
this._update_persistence();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// call a base gdpr function after constructing the appropriate token and options args
|
|
|
|
|
MixpanelLib.prototype._call_gdpr_func = function(func, options) {
|
|
|
|
|
options = _.extend({
|
|
|
|
|
'track': _.bind(this.track, this),
|
|
|
|
|
'persistence_type': this.get_config('opt_out_tracking_persistence_type'),
|
|
|
|
|
'cookie_prefix': this.get_config('opt_out_tracking_cookie_prefix'),
|
|
|
|
|
'cookie_expiration': this.get_config('cookie_expiration'),
|
|
|
|
|
'cross_subdomain_cookie': this.get_config('cross_subdomain_cookie'),
|
|
|
|
|
'secure_cookie': this.get_config('secure_cookie')
|
|
|
|
|
}, options);
|
|
|
|
|
|
|
|
|
|
// check if localStorage can be used for recording opt out status, fall back to cookie if not
|
|
|
|
|
if (!_.localStorage.is_supported()) {
|
|
|
|
|
options['persistence_type'] = 'cookie';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return func(this.get_config('token'), {
|
|
|
|
|
track: options['track'],
|
|
|
|
|
trackEventName: options['track_event_name'],
|
|
|
|
|
trackProperties: options['track_properties'],
|
|
|
|
|
persistenceType: options['persistence_type'],
|
|
|
|
|
persistencePrefix: options['cookie_prefix'],
|
|
|
|
|
cookieExpiration: options['cookie_expiration'],
|
|
|
|
|
crossSubdomainCookie: options['cross_subdomain_cookie'],
|
|
|
|
|
secureCookie: options['secure_cookie']
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Opt the user in to data tracking and cookies/localstorage for this Mixpanel instance
|
|
|
|
|
*
|
|
|
|
|
* ### Usage
|
|
|
|
|
*
|
|
|
|
|
* // opt user in
|
|
|
|
|
* mixpanel.opt_in_tracking();
|
|
|
|
|
*
|
|
|
|
|
* // opt user in with specific event name, properties, cookie configuration
|
|
|
|
|
* mixpanel.opt_in_tracking({
|
|
|
|
|
* track_event_name: 'User opted in',
|
|
|
|
|
* track_event_properties: {
|
|
|
|
|
* 'Email': 'jdoe@example.com'
|
|
|
|
|
* },
|
|
|
|
|
* cookie_expiration: 30,
|
|
|
|
|
* secure_cookie: true
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} [options] A dictionary of config options to override
|
|
|
|
|
* @param {function} [options.track] Function used for tracking a Mixpanel event to record the opt-in action (default is this Mixpanel instance's track method)
|
|
|
|
|
* @param {string} [options.track_event_name=$opt_in] Event name to be used for tracking the opt-in action
|
|
|
|
|
* @param {Object} [options.track_properties] Set of properties to be tracked along with the opt-in action
|
|
|
|
|
* @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable
|
|
|
|
|
* @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name
|
|
|
|
|
* @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config)
|
|
|
|
|
* @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config)
|
|
|
|
|
* @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config)
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.opt_in_tracking = function(options) {
|
|
|
|
|
this._call_gdpr_func(optIn, options);
|
|
|
|
|
this._update_persistence();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Opt the user out of data tracking and cookies/localstorage for this Mixpanel instance
|
|
|
|
|
*
|
|
|
|
|
* ### Usage
|
|
|
|
|
*
|
|
|
|
|
* // opt user out
|
|
|
|
|
* mixpanel.opt_out_tracking();
|
|
|
|
|
*
|
|
|
|
|
* // opt user out with different cookie configuration from Mixpanel instance
|
|
|
|
|
* mixpanel.opt_out_tracking({
|
|
|
|
|
* cookie_expiration: 30,
|
|
|
|
|
* secure_cookie: true
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} [options] A dictionary of config options to override
|
|
|
|
|
* @param {boolean} [options.delete_user=true] If true, will delete the currently identified user's profile and clear all charges after opting the user out
|
|
|
|
|
* @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable
|
|
|
|
|
* @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name
|
|
|
|
|
* @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config)
|
|
|
|
|
* @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config)
|
|
|
|
|
* @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config)
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.opt_out_tracking = function(options) {
|
|
|
|
|
// delete use and clear charges since these methods may be disabled by opt-out
|
|
|
|
|
options = _.extend({'delete_user': true}, options);
|
|
|
|
|
if (options['delete_user'] && this['people'] && this['people']._identify_called()) {
|
|
|
|
|
this['people'].delete_user();
|
|
|
|
|
this['people'].clear_charges();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._call_gdpr_func(optOut, options);
|
|
|
|
|
this._update_persistence();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check whether the user has opted in to data tracking and cookies/localstorage for this Mixpanel instance
|
|
|
|
|
*
|
|
|
|
|
* ### Usage
|
|
|
|
|
*
|
|
|
|
|
* var has_opted_in = mixpanel.has_opted_in_tracking();
|
|
|
|
|
* // use has_opted_in value
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} [options] A dictionary of config options to override
|
|
|
|
|
* @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable
|
|
|
|
|
* @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name
|
|
|
|
|
* @returns {boolean} current opt-in status
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.has_opted_in_tracking = function(options) {
|
|
|
|
|
return this._call_gdpr_func(hasOptedIn, options);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check whether the user has opted out of data tracking and cookies/localstorage for this Mixpanel instance
|
|
|
|
|
*
|
|
|
|
|
* ### Usage
|
|
|
|
|
*
|
|
|
|
|
* var has_opted_out = mixpanel.has_opted_out_tracking();
|
|
|
|
|
* // use has_opted_out value
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} [options] A dictionary of config options to override
|
|
|
|
|
* @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable
|
|
|
|
|
* @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name
|
|
|
|
|
* @returns {boolean} current opt-out status
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.has_opted_out_tracking = function(options) {
|
|
|
|
|
return this._call_gdpr_func(hasOptedOut, options);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clear the user's opt in/out status of data tracking and cookies/localstorage for this Mixpanel instance
|
|
|
|
|
*
|
|
|
|
|
* ### Usage
|
|
|
|
|
*
|
|
|
|
|
* // clear user's opt-in/out status
|
|
|
|
|
* mixpanel.clear_opt_in_out_tracking();
|
|
|
|
|
*
|
|
|
|
|
* // clear user's opt-in/out status with specific cookie configuration - should match
|
|
|
|
|
* // configuration used when opt_in_tracking/opt_out_tracking methods were called.
|
|
|
|
|
* mixpanel.clear_opt_in_out_tracking({
|
|
|
|
|
* cookie_expiration: 30,
|
|
|
|
|
* secure_cookie: true
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} [options] A dictionary of config options to override
|
|
|
|
|
* @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable
|
|
|
|
|
* @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name
|
|
|
|
|
* @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config)
|
|
|
|
|
* @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config)
|
|
|
|
|
* @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config)
|
|
|
|
|
*/
|
|
|
|
|
MixpanelLib.prototype.clear_opt_in_out_tracking = function(options) {
|
|
|
|
|
this._call_gdpr_func(clearOptInOut, options);
|
|
|
|
|
this._update_persistence();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MixpanelPeople.prototype._init = function(mixpanel_instance) {
|
|
|
|
|
this._mixpanel = mixpanel_instance;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Set properties on a user record.
|
|
|
|
|
*
|
|
|
|
|
* ### Usage:
|
|
|
|
|
*
|
|
|
|
|
* mixpanel.people.set('gender', 'm');
|
|
|
|
|
*
|
|
|
|
|
* // or set multiple properties at once
|
|
|
|
|
* mixpanel.people.set({
|
|
|
|
|
* 'Company': 'Acme',
|
|
|
|
|
* 'Plan': 'Premium',
|
|
|
|
|
* 'Upgrade date': new Date()
|
|
|
|
|
* });
|
|
|
|
|
* // properties can be strings, integers, dates, or lists
|
|
|
|
|
*
|
|
|
|
|
* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values.
|
|
|
|
|
* @param {*} [to] A value to set on the given property name
|
|
|
|
|
* @param {Function} [callback] If provided, the callback will be called after the tracking event
|
|
|
|
|
*/
|
|
|
|
|
MixpanelPeople.prototype.set = addOptOutCheckMixpanelPeople(function(prop, to, callback) {
|
|
|
|
|
var data = {};
|
|
|
|
|
var $set = {};
|
|
|
|
|
if (_.isObject(prop)) {
|
|
|
|
|
_.each(prop, function(v, k) {
|
|
|
|
|
if (!this._is_reserved_property(k)) {
|
|
|
|
|
$set[k] = v;
|
|
|
|
|
}
|
|
|
|
|
}, this);
|
|
|
|
|
callback = to;
|
|
|
|
|
} else {
|
|
|
|
|
$set[prop] = to;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// make sure that the referrer info has been updated and saved
|
|
|
|
|
if (this._get_config('save_referrer')) {
|
|
|
|
|
this._mixpanel['persistence'].update_referrer_info(document$1.referrer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// update $set object with default people properties
|
|
|
|
|
$set = _.extend(
|
|
|
|
|
{},
|
|
|
|
|
_.info.people_properties(),
|
|
|
|
|
this._mixpanel['persistence'].get_referrer_info(),
|
|
|
|
|
$set
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
data[SET_ACTION] = $set;
|
|
|
|
|
|
|
|
|
|
return this._send_request(data, callback);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Set properties on a user record, only if they do not yet exist.
|
|
|
|
|
* This will not overwrite previous people property values, unlike
|
|
|
|
|
* people.set().
|
|
|
|
|
*
|
|
|
|
|
* ### Usage:
|
|
|
|
|
*
|
|
|
|
|
* mixpanel.people.set_once('First Login Date', new Date());
|
|
|
|
|
*
|
|
|
|
|
* // or set multiple properties at once
|
|
|
|
|
* mixpanel.people.set_once({
|
|
|
|
|
* 'First Login Date': new Date(),
|
|
|
|
|
* 'Starting Plan': 'Premium'
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // properties can be strings, integers or dates
|
|
|
|
|
*
|
|
|
|
|
* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values.
|
|
|
|
|
* @param {*} [to] A value to set on the given property name
|
|
|
|
|
* @param {Function} [callback] If provided, the callback will be called after the tracking event
|
|
|
|
|
*/
|
|
|
|
|
MixpanelPeople.prototype.set_once = addOptOutCheckMixpanelPeople(function(prop, to, callback) {
|
|
|
|
|
var data = {};
|
|
|
|
|
var $set_once = {};
|
|
|
|
|
if (_.isObject(prop)) {
|
|
|
|
|
_.each(prop, function(v, k) {
|
|
|
|
|
if (!this._is_reserved_property(k)) {
|
|
|
|
|
$set_once[k] = v;
|
|
|
|
|
}
|
|
|
|
|
}, this);
|
|
|
|
|
callback = to;
|
|
|
|
|
} else {
|
|
|
|
|
$set_once[prop] = to;
|
|
|
|
|
}
|
|
|
|
|
data[SET_ONCE_ACTION] = $set_once;
|
|
|
|
|
return this._send_request(data, callback);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Unset properties on a user record (permanently removes the properties and their values from a profile).
|
|
|
|
|
*
|
|
|
|
|
* ### Usage:
|
|
|
|
|
*
|
|
|
|
|
* mixpanel.people.unset('gender');
|
|
|
|
|
*
|
|
|
|
|
* // or unset multiple properties at once
|
|
|
|
|
* mixpanel.people.unset(['gender', 'Company']);
|
|
|
|
|
*
|
|
|
|
|
* @param {Array|String} prop If a string, this is the name of the property. If an array, this is a list of property names.
|
|
|
|
|
* @param {Function} [callback] If provided, the callback will be called after the tracking event
|
|
|
|
|
*/
|
|
|
|
|
MixpanelPeople.prototype.unset = function(prop, callback) {
|
|
|
|
|
var data = {};
|
|
|
|
|
var $unset = [];
|
|
|
|
|
if (!_.isArray(prop)) {
|
|
|
|
|
prop = [prop];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_.each(prop, function(k) {
|
|
|
|
|
if (!this._is_reserved_property(k)) {
|
|
|
|
|
$unset.push(k);
|
|
|
|
|
}
|
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
|
|
data[UNSET_ACTION] = $unset;
|
|
|
|
|
|
|
|
|
|
return this._send_request(data, callback);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Increment/decrement numeric people analytics properties.
|
|
|
|
|
*
|
|
|
|
|
* ### Usage:
|
|
|
|
|
*
|
|
|
|
|
* mixpanel.people.increment('page_views', 1);
|
|
|
|
|
*
|
|
|
|
|
* // or, for convenience, if you're just incrementing a counter by
|
|
|
|
|
* // 1, you can simply do
|
|
|
|
|
* mixpanel.people.increment('page_views');
|
|
|
|
|
*
|
|
|
|
|
* // to decrement a counter, pass a negative number
|
|
|
|
|
* mixpanel.people.increment('credits_left', -1);
|
|
|
|
|
*
|
|
|
|
|
* // like mixpanel.people.set(), you can increment multiple
|
|
|
|
|
* // properties at once:
|
|
|
|
|
* mixpanel.people.increment({
|
|
|
|
|
* counter1: 1,
|
|
|
|
|
* counter2: 6
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and numeric values.
|
|
|
|
|
* @param {Number} [by] An amount to increment the given property
|
|
|
|
|
* @param {Function} [callback] If provided, the callback will be called after the tracking event
|
|
|
|
|
*/
|
|
|
|
|
MixpanelPeople.prototype.increment = addOptOutCheckMixpanelPeople(function(prop, by, callback) {
|
|
|
|
|
var data = {};
|
|
|
|
|
var $add = {};
|
|
|
|
|
if (_.isObject(prop)) {
|
|
|
|
|
_.each(prop, function(v, k) {
|
|
|
|
|
if (!this._is_reserved_property(k)) {
|
|
|
|
|
if (isNaN(parseFloat(v))) {
|
|
|
|
|
console$1.error('Invalid increment value passed to mixpanel.people.increment - must be a number');
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
$add[k] = v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, this);
|
|
|
|
|
callback = by;
|
|
|
|
|
} else {
|
|
|
|
|
// convenience: mixpanel.people.increment('property'); will
|
|
|
|
|
// increment 'property' by 1
|
|
|
|
|
if (_.isUndefined(by)) {
|
|
|
|
|
by = 1;
|
|
|
|
|
}
|
|
|
|
|
$add[prop] = by;
|
|
|
|
|
}
|
|
|
|
|
data[ADD_ACTION] = $add;
|
|
|
|
|
|
|
|
|
|
return this._send_request(data, callback);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Append a value to a list-valued people analytics property.
|
|
|
|
|
*
|
|
|
|
|
* ### Usage:
|
|
|
|
|
*
|
|
|
|
|
* // append a value to a list, creating it if needed
|
|
|
|
|
* mixpanel.people.append('pages_visited', 'homepage');
|
|
|
|
|
*
|
|
|
|
|
* // like mixpanel.people.set(), you can append multiple
|
|
|
|
|
* // properties at once:
|
|
|
|
|
* mixpanel.people.append({
|
|
|
|
|
* list1: 'bob',
|
|
|
|
|
* list2: 123
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values.
|
|
|
|
|
* @param {*} [value] An item to append to the list
|
|
|
|
|
* @param {Function} [callback] If provided, the callback will be called after the tracking event
|
|
|
|
|
*/
|
|
|
|
|
MixpanelPeople.prototype.append = addOptOutCheckMixpanelPeople(function(list_name, value, callback) {
|
|
|
|
|
var data = {};
|
|
|
|
|
var $append = {};
|
|
|
|
|
if (_.isObject(list_name)) {
|
|
|
|
|
_.each(list_name, function(v, k) {
|
|
|
|
|
if (!this._is_reserved_property(k)) {
|
|
|
|
|
$append[k] = v;
|
|
|
|
|
}
|
|
|
|
|
}, this);
|
|
|
|
|
callback = value;
|
|
|
|
|
} else {
|
|
|
|
|
$append[list_name] = value;
|
|
|
|
|
}
|
|
|
|
|
data[APPEND_ACTION] = $append;
|
|
|
|
|
|
|
|
|
|
return this._send_request(data, callback);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Merge a given list with a list-valued people analytics property,
|
|
|
|
|
* excluding duplicate values.
|
|
|
|
|
*
|
|
|
|
|
* ### Usage:
|
|
|
|
|
*
|
|
|
|
|
* // merge a value to a list, creating it if needed
|
|
|
|
|
* mixpanel.people.union('pages_visited', 'homepage');
|
|
|
|
|
*
|
|
|
|
|
* // like mixpanel.people.set(), you can append multiple
|
|
|
|
|
* // properties at once:
|
|
|
|
|
* mixpanel.people.union({
|
|
|
|
|
* list1: 'bob',
|
|
|
|
|
* list2: 123
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // like mixpanel.people.append(), you can append multiple
|
|
|
|
|
* // values to the same list:
|
|
|
|
|
* mixpanel.people.union({
|
|
|
|
|
* list1: ['bob', 'billy']
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values.
|
|
|
|
|
* @param {*} [value] Value / values to merge with the given property
|
|
|
|
|
* @param {Function} [callback] If provided, the callback will be called after the tracking event
|
|
|
|
|
*/
|
|
|
|
|
MixpanelPeople.prototype.union = addOptOutCheckMixpanelPeople(function(list_name, values, callback) {
|
|
|
|
|
var data = {};
|
|
|
|
|
var $union = {};
|
|
|
|
|
if (_.isObject(list_name)) {
|
|
|
|
|
_.each(list_name, function(v, k) {
|
|
|
|
|
if (!this._is_reserved_property(k)) {
|
|
|
|
|
$union[k] = _.isArray(v) ? v : [v];
|
|
|
|
|
}
|
|
|
|
|
}, this);
|
|
|
|
|
callback = values;
|
|
|
|
|
} else {
|
|
|
|
|
$union[list_name] = _.isArray(values) ? values : [values];
|
|
|
|
|
}
|
|
|
|
|
data[UNION_ACTION] = $union;
|
|
|
|
|
|
|
|
|
|
return this._send_request(data, callback);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Record that you have charged the current user a certain amount
|
|
|
|
|
* of money. Charges recorded with track_charge() will appear in the
|
|
|
|
|
* Mixpanel revenue report.
|
|
|
|
|
*
|
|
|
|
|
* ### Usage:
|
|
|
|
|
*
|
|
|
|
|
* // charge a user $50
|
|
|
|
|
* mixpanel.people.track_charge(50);
|
|
|
|
|
*
|
|
|
|
|
* // charge a user $30.50 on the 2nd of january
|
|
|
|
|
* mixpanel.people.track_charge(30.50, {
|
|
|
|
|
* '$time': new Date('jan 1 2012')
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* @param {Number} amount The amount of money charged to the current user
|
|
|
|
|
* @param {Object} [properties] An associative array of properties associated with the charge
|
|
|
|
|
* @param {Function} [callback] If provided, the callback will be called when the server responds
|
|
|
|
|
*/
|
|
|
|
|
MixpanelPeople.prototype.track_charge = addOptOutCheckMixpanelPeople(function(amount, properties, callback) {
|
|
|
|
|
if (!_.isNumber(amount)) {
|
|
|
|
|
amount = parseFloat(amount);
|
|
|
|
|
if (isNaN(amount)) {
|
|
|
|
|
console$1.error('Invalid value passed to mixpanel.people.track_charge - must be a number');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.append('$transactions', _.extend({
|
|
|
|
|
'$amount': amount
|
|
|
|
|
}, properties), callback);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Permanently clear all revenue report transactions from the
|
|
|
|
|
* current user's people analytics profile.
|
|
|
|
|
*
|
|
|
|
|
* ### Usage:
|
|
|
|
|
*
|
|
|
|
|
* mixpanel.people.clear_charges();
|
|
|
|
|
*
|
|
|
|
|
* @param {Function} [callback] If provided, the callback will be called after the tracking event
|
|
|
|
|
*/
|
|
|
|
|
MixpanelPeople.prototype.clear_charges = function(callback) {
|
|
|
|
|
return this.set('$transactions', [], callback);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Permanently deletes the current people analytics profile from
|
|
|
|
|
* Mixpanel (using the current distinct_id).
|
|
|
|
|
*
|
|
|
|
|
* ### Usage:
|
|
|
|
|
*
|
|
|
|
|
* // remove the all data you have stored about the current user
|
|
|
|
|
* mixpanel.people.delete_user();
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
MixpanelPeople.prototype.delete_user = function() {
|
|
|
|
|
if (!this._identify_called()) {
|
|
|
|
|
console$1.error('mixpanel.people.delete_user() requires you to call identify() first');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var data = {'$delete': this._mixpanel.get_distinct_id()};
|
|
|
|
|
return this._send_request(data);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPeople.prototype.toString = function() {
|
|
|
|
|
return this._mixpanel.toString() + '.people';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPeople.prototype._send_request = function(data, callback) {
|
|
|
|
|
data['$token'] = this._get_config('token');
|
|
|
|
|
data['$distinct_id'] = this._mixpanel.get_distinct_id();
|
|
|
|
|
|
|
|
|
|
var date_encoded_data = _.encodeDates(data);
|
|
|
|
|
var truncated_data = _.truncate(date_encoded_data, 255);
|
|
|
|
|
var json_data = _.JSONEncode(date_encoded_data);
|
|
|
|
|
var encoded_data = _.base64Encode(json_data);
|
|
|
|
|
|
|
|
|
|
if (!this._identify_called()) {
|
|
|
|
|
this._enqueue(data);
|
|
|
|
|
if (!_.isUndefined(callback)) {
|
|
|
|
|
if (this._get_config('verbose')) {
|
|
|
|
|
callback({status: -1, error: null});
|
|
|
|
|
} else {
|
|
|
|
|
callback(-1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return truncated_data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console$1.log('MIXPANEL PEOPLE REQUEST:');
|
|
|
|
|
console$1.log(truncated_data);
|
|
|
|
|
|
|
|
|
|
this._mixpanel._send_request(
|
|
|
|
|
this._get_config('api_host') + '/engage/',
|
|
|
|
|
{'data': encoded_data},
|
|
|
|
|
this._mixpanel._prepare_callback(callback, truncated_data)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return truncated_data;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPeople.prototype._get_config = function(conf_var) {
|
|
|
|
|
return this._mixpanel.get_config(conf_var);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPeople.prototype._identify_called = function() {
|
|
|
|
|
return this._mixpanel._flags.identify_called === true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Queue up engage operations if identify hasn't been called yet.
|
|
|
|
|
MixpanelPeople.prototype._enqueue = function(data) {
|
|
|
|
|
if (SET_ACTION in data) {
|
|
|
|
|
this._mixpanel['persistence']._add_to_people_queue(SET_ACTION, data);
|
|
|
|
|
} else if (SET_ONCE_ACTION in data) {
|
|
|
|
|
this._mixpanel['persistence']._add_to_people_queue(SET_ONCE_ACTION, data);
|
|
|
|
|
} else if (UNSET_ACTION in data) {
|
|
|
|
|
this._mixpanel['persistence']._add_to_people_queue(UNSET_ACTION, data);
|
|
|
|
|
} else if (ADD_ACTION in data) {
|
|
|
|
|
this._mixpanel['persistence']._add_to_people_queue(ADD_ACTION, data);
|
|
|
|
|
} else if (APPEND_ACTION in data) {
|
|
|
|
|
this._mixpanel['persistence']._add_to_people_queue(APPEND_ACTION, data);
|
|
|
|
|
} else if (UNION_ACTION in data) {
|
|
|
|
|
this._mixpanel['persistence']._add_to_people_queue(UNION_ACTION, data);
|
|
|
|
|
} else {
|
|
|
|
|
console$1.error('Invalid call to _enqueue():', data);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPeople.prototype._flush_one_queue = function(action, action_method, callback, queue_to_params_fn) {
|
|
|
|
|
var _this = this;
|
|
|
|
|
var queued_data = _.extend({}, this._mixpanel['persistence']._get_queue(action));
|
|
|
|
|
var action_params = queued_data;
|
|
|
|
|
|
|
|
|
|
if (!_.isUndefined(queued_data) && _.isObject(queued_data) && !_.isEmptyObject(queued_data)) {
|
|
|
|
|
_this._mixpanel['persistence']._pop_from_people_queue(action, queued_data);
|
|
|
|
|
if (queue_to_params_fn) {
|
|
|
|
|
action_params = queue_to_params_fn(queued_data);
|
|
|
|
|
}
|
|
|
|
|
action_method.call(_this, action_params, function(response, data) {
|
|
|
|
|
// on bad response, we want to add it back to the queue
|
|
|
|
|
if (response === 0) {
|
|
|
|
|
_this._mixpanel['persistence']._add_to_people_queue(action, queued_data);
|
|
|
|
|
}
|
|
|
|
|
if (!_.isUndefined(callback)) {
|
|
|
|
|
callback(response, data);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Flush queued engage operations - order does not matter,
|
|
|
|
|
// and there are network level race conditions anyway
|
|
|
|
|
MixpanelPeople.prototype._flush = function(
|
|
|
|
|
_set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback
|
|
|
|
|
) {
|
|
|
|
|
var _this = this;
|
|
|
|
|
var $append_queue = this._mixpanel['persistence']._get_queue(APPEND_ACTION);
|
|
|
|
|
|
|
|
|
|
this._flush_one_queue(SET_ACTION, this.set, _set_callback);
|
|
|
|
|
this._flush_one_queue(SET_ONCE_ACTION, this.set_once, _set_once_callback);
|
|
|
|
|
this._flush_one_queue(UNSET_ACTION, this.unset, _unset_callback, function(queue) { return _.keys(queue); });
|
|
|
|
|
this._flush_one_queue(ADD_ACTION, this.increment, _add_callback);
|
|
|
|
|
this._flush_one_queue(UNION_ACTION, this.union, _union_callback);
|
|
|
|
|
|
|
|
|
|
// we have to fire off each $append individually since there is
|
|
|
|
|
// no concat method server side
|
|
|
|
|
if (!_.isUndefined($append_queue) && _.isArray($append_queue) && $append_queue.length) {
|
|
|
|
|
var $append_item;
|
|
|
|
|
var callback = function(response, data) {
|
|
|
|
|
if (response === 0) {
|
|
|
|
|
_this._mixpanel['persistence']._add_to_people_queue(APPEND_ACTION, $append_item);
|
|
|
|
|
}
|
|
|
|
|
if (!_.isUndefined(_append_callback)) {
|
|
|
|
|
_append_callback(response, data);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
for (var i = $append_queue.length - 1; i >= 0; i--) {
|
|
|
|
|
$append_item = $append_queue.pop();
|
|
|
|
|
_this.append($append_item, callback);
|
|
|
|
|
}
|
|
|
|
|
// Save the shortened append queue
|
|
|
|
|
_this._mixpanel['persistence'].save();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MixpanelPeople.prototype._is_reserved_property = function(prop) {
|
|
|
|
|
return prop === '$distinct_id' || prop === '$token';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Internal class for notification display
|
|
|
|
|
MixpanelLib._Notification = function(notif_data, mixpanel_instance) {
|
|
|
|
|
_.bind_instance_methods(this);
|
|
|
|
|
|
|
|
|
|
this.mixpanel = mixpanel_instance;
|
|
|
|
|
this.persistence = this.mixpanel['persistence'];
|
|
|
|
|
|
|
|
|
|
this.campaign_id = _.escapeHTML(notif_data['id']);
|
|
|
|
|
this.message_id = _.escapeHTML(notif_data['message_id']);
|
|
|
|
|
|
|
|
|
|
this.body = (_.escapeHTML(notif_data['body']) || '').replace(/\n/g, '<br/>');
|
|
|
|
|
this.cta = _.escapeHTML(notif_data['cta']) || 'Close';
|
|
|
|
|
this.notif_type = _.escapeHTML(notif_data['type']) || 'takeover';
|
|
|
|
|
this.style = _.escapeHTML(notif_data['style']) || 'light';
|
|
|
|
|
this.title = _.escapeHTML(notif_data['title']) || '';
|
|
|
|
|
this.video_width = MPNotif.VIDEO_WIDTH;
|
|
|
|
|
this.video_height = MPNotif.VIDEO_HEIGHT;
|
|
|
|
|
|
|
|
|
|
// These fields are url-sanitized in the backend already.
|
|
|
|
|
this.dest_url = notif_data['cta_url'] || null;
|
|
|
|
|
this.image_url = notif_data['image_url'] || null;
|
|
|
|
|
this.thumb_image_url = notif_data['thumb_image_url'] || null;
|
|
|
|
|
this.video_url = notif_data['video_url'] || null;
|
|
|
|
|
|
|
|
|
|
this.clickthrough = true;
|
|
|
|
|
if (!this.dest_url) {
|
|
|
|
|
this.dest_url = '#dismiss';
|
|
|
|
|
this.clickthrough = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.mini = this.notif_type === 'mini';
|
|
|
|
|
if (!this.mini) {
|
|
|
|
|
this.notif_type = 'takeover';
|
|
|
|
|
}
|
|
|
|
|
this.notif_width = !this.mini ? MPNotif.NOTIF_WIDTH : MPNotif.NOTIF_WIDTH_MINI;
|
|
|
|
|
|
|
|
|
|
this._set_client_config();
|
|
|
|
|
this.imgs_to_preload = this._init_image_html();
|
|
|
|
|
this._init_video();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MPNotif = MixpanelLib._Notification;
|
|
|
|
|
|
|
|
|
|
MPNotif.ANIM_TIME = 200;
|
|
|
|
|
MPNotif.MARKUP_PREFIX = 'mixpanel-notification';
|
|
|
|
|
MPNotif.BG_OPACITY = 0.6;
|
|
|
|
|
MPNotif.NOTIF_TOP = 25;
|
|
|
|
|
MPNotif.NOTIF_START_TOP = 200;
|
|
|
|
|
MPNotif.NOTIF_WIDTH = 388;
|
|
|
|
|
MPNotif.NOTIF_WIDTH_MINI = 420;
|
|
|
|
|
MPNotif.NOTIF_HEIGHT_MINI = 85;
|
|
|
|
|
MPNotif.THUMB_BORDER_SIZE = 5;
|
|
|
|
|
MPNotif.THUMB_IMG_SIZE = 60;
|
|
|
|
|
MPNotif.THUMB_OFFSET = Math.round(MPNotif.THUMB_IMG_SIZE / 2);
|
|
|
|
|
MPNotif.VIDEO_WIDTH = 595;
|
|
|
|
|
MPNotif.VIDEO_HEIGHT = 334;
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype.show = function() {
|
|
|
|
|
var self = this;
|
|
|
|
|
this._set_client_config();
|
|
|
|
|
|
|
|
|
|
// don't display until HTML body exists
|
|
|
|
|
if (!this.body_el) {
|
|
|
|
|
setTimeout(function() { self.show(); }, 300);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._init_styles();
|
|
|
|
|
this._init_notification_el();
|
|
|
|
|
|
|
|
|
|
// wait for any images to load before showing notification
|
|
|
|
|
this._preload_images(this._attach_and_animate);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype.dismiss = _.safewrap(function() {
|
|
|
|
|
if (!this.marked_as_shown) {
|
|
|
|
|
// unexpected condition: user interacted with notif even though we didn't consider it
|
|
|
|
|
// visible (see _mark_as_shown()); send tracking signals to mark delivery
|
|
|
|
|
this._mark_delivery({'invisible': true});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var exiting_el = this.showing_video ? this._get_el('video') : this._get_notification_display_el();
|
|
|
|
|
if (this.use_transitions) {
|
|
|
|
|
this._remove_class('bg', 'visible');
|
|
|
|
|
this._add_class(exiting_el, 'exiting');
|
|
|
|
|
setTimeout(this._remove_notification_el, MPNotif.ANIM_TIME);
|
|
|
|
|
} else {
|
|
|
|
|
var notif_attr, notif_start, notif_goal;
|
|
|
|
|
if (this.mini) {
|
|
|
|
|
notif_attr = 'right';
|
|
|
|
|
notif_start = 20;
|
|
|
|
|
notif_goal = -100;
|
|
|
|
|
} else {
|
|
|
|
|
notif_attr = 'top';
|
|
|
|
|
notif_start = MPNotif.NOTIF_TOP;
|
|
|
|
|
notif_goal = MPNotif.NOTIF_START_TOP + MPNotif.NOTIF_TOP;
|
|
|
|
|
}
|
|
|
|
|
this._animate_els([
|
|
|
|
|
{
|
|
|
|
|
el: this._get_el('bg'),
|
|
|
|
|
attr: 'opacity',
|
|
|
|
|
start: MPNotif.BG_OPACITY,
|
|
|
|
|
goal: 0.0
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
el: exiting_el,
|
|
|
|
|
attr: 'opacity',
|
|
|
|
|
start: 1.0,
|
|
|
|
|
goal: 0.0
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
el: exiting_el,
|
|
|
|
|
attr: notif_attr,
|
|
|
|
|
start: notif_start,
|
|
|
|
|
goal: notif_goal
|
|
|
|
|
}
|
|
|
|
|
], MPNotif.ANIM_TIME, this._remove_notification_el);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._add_class = _.safewrap(function(el, class_name) {
|
|
|
|
|
class_name = MPNotif.MARKUP_PREFIX + '-' + class_name;
|
|
|
|
|
if (typeof el === 'string') {
|
|
|
|
|
el = this._get_el(el);
|
|
|
|
|
}
|
|
|
|
|
if (!el.className) {
|
|
|
|
|
el.className = class_name;
|
|
|
|
|
} else if (!~(' ' + el.className + ' ').indexOf(' ' + class_name + ' ')) {
|
|
|
|
|
el.className += ' ' + class_name;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
MPNotif.prototype._remove_class = _.safewrap(function(el, class_name) {
|
|
|
|
|
class_name = MPNotif.MARKUP_PREFIX + '-' + class_name;
|
|
|
|
|
if (typeof el === 'string') {
|
|
|
|
|
el = this._get_el(el);
|
|
|
|
|
}
|
|
|
|
|
if (el.className) {
|
|
|
|
|
el.className = (' ' + el.className + ' ')
|
|
|
|
|
.replace(' ' + class_name + ' ', '')
|
|
|
|
|
.replace(/^[\s\xA0]+/, '')
|
|
|
|
|
.replace(/[\s\xA0]+$/, '');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._animate_els = _.safewrap(function(anims, mss, done_cb, start_time) {
|
|
|
|
|
var self = this,
|
|
|
|
|
in_progress = false,
|
|
|
|
|
ai, anim,
|
|
|
|
|
cur_time = 1 * new Date(), time_diff;
|
|
|
|
|
|
|
|
|
|
start_time = start_time || cur_time;
|
|
|
|
|
time_diff = cur_time - start_time;
|
|
|
|
|
|
|
|
|
|
for (ai = 0; ai < anims.length; ai++) {
|
|
|
|
|
anim = anims[ai];
|
|
|
|
|
if (typeof anim.val === 'undefined') {
|
|
|
|
|
anim.val = anim.start;
|
|
|
|
|
}
|
|
|
|
|
if (anim.val !== anim.goal) {
|
|
|
|
|
in_progress = true;
|
|
|
|
|
var anim_diff = anim.goal - anim.start,
|
|
|
|
|
anim_dir = anim.goal >= anim.start ? 1 : -1;
|
|
|
|
|
anim.val = anim.start + anim_diff * time_diff / mss;
|
|
|
|
|
if (anim.attr !== 'opacity') {
|
|
|
|
|
anim.val = Math.round(anim.val);
|
|
|
|
|
}
|
|
|
|
|
if ((anim_dir > 0 && anim.val >= anim.goal) || (anim_dir < 0 && anim.val <= anim.goal)) {
|
|
|
|
|
anim.val = anim.goal;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!in_progress) {
|
|
|
|
|
if (done_cb) {
|
|
|
|
|
done_cb();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (ai = 0; ai < anims.length; ai++) {
|
|
|
|
|
anim = anims[ai];
|
|
|
|
|
if (anim.el) {
|
|
|
|
|
var suffix = anim.attr === 'opacity' ? '' : 'px';
|
|
|
|
|
anim.el.style[anim.attr] = String(anim.val) + suffix;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
setTimeout(function() { self._animate_els(anims, mss, done_cb, start_time); }, 10);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._attach_and_animate = _.safewrap(function() {
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
|
|
// no possibility to double-display
|
|
|
|
|
if (this.shown || this._get_shown_campaigns()[this.campaign_id]) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.shown = true;
|
|
|
|
|
|
|
|
|
|
this.body_el.appendChild(this.notification_el);
|
|
|
|
|
setTimeout(function() {
|
|
|
|
|
var notif_el = self._get_notification_display_el();
|
|
|
|
|
if (self.use_transitions) {
|
|
|
|
|
if (!self.mini) {
|
|
|
|
|
self._add_class('bg', 'visible');
|
|
|
|
|
}
|
|
|
|
|
self._add_class(notif_el, 'visible');
|
|
|
|
|
self._mark_as_shown();
|
|
|
|
|
} else {
|
|
|
|
|
var notif_attr, notif_start, notif_goal;
|
|
|
|
|
if (self.mini) {
|
|
|
|
|
notif_attr = 'right';
|
|
|
|
|
notif_start = -100;
|
|
|
|
|
notif_goal = 20;
|
|
|
|
|
} else {
|
|
|
|
|
notif_attr = 'top';
|
|
|
|
|
notif_start = MPNotif.NOTIF_START_TOP + MPNotif.NOTIF_TOP;
|
|
|
|
|
notif_goal = MPNotif.NOTIF_TOP;
|
|
|
|
|
}
|
|
|
|
|
self._animate_els([
|
|
|
|
|
{
|
|
|
|
|
el: self._get_el('bg'),
|
|
|
|
|
attr: 'opacity',
|
|
|
|
|
start: 0.0,
|
|
|
|
|
goal: MPNotif.BG_OPACITY
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
el: notif_el,
|
|
|
|
|
attr: 'opacity',
|
|
|
|
|
start: 0.0,
|
|
|
|
|
goal: 1.0
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
el: notif_el,
|
|
|
|
|
attr: notif_attr,
|
|
|
|
|
start: notif_start,
|
|
|
|
|
goal: notif_goal
|
|
|
|
|
}
|
|
|
|
|
], MPNotif.ANIM_TIME, self._mark_as_shown);
|
|
|
|
|
}
|
|
|
|
|
}, 100);
|
|
|
|
|
_.register_event(self._get_el('cancel'), 'click', function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
self.dismiss();
|
|
|
|
|
});
|
|
|
|
|
var click_el = self._get_el('button') ||
|
|
|
|
|
self._get_el('mini-content');
|
|
|
|
|
_.register_event(click_el, 'click', function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
if (self.show_video) {
|
|
|
|
|
self._track_event('$campaign_open', {'$resource_type': 'video'});
|
|
|
|
|
self._switch_to_video();
|
|
|
|
|
} else {
|
|
|
|
|
self.dismiss();
|
|
|
|
|
if (self.clickthrough) {
|
|
|
|
|
self._track_event('$campaign_open', {'$resource_type': 'link'}, function() {
|
|
|
|
|
window$1.location.href = self.dest_url;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._get_el = function(id) {
|
|
|
|
|
return document$1.getElementById(MPNotif.MARKUP_PREFIX + '-' + id);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._get_notification_display_el = function() {
|
|
|
|
|
return this._get_el(this.notif_type);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._get_shown_campaigns = function() {
|
|
|
|
|
return this.persistence['props'][CAMPAIGN_IDS_KEY] || (this.persistence['props'][CAMPAIGN_IDS_KEY] = {});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._browser_lte = function(browser, version) {
|
|
|
|
|
return this.browser_versions[browser] && this.browser_versions[browser] <= version;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._init_image_html = function() {
|
|
|
|
|
var imgs_to_preload = [];
|
|
|
|
|
|
|
|
|
|
if (!this.mini) {
|
|
|
|
|
if (this.image_url) {
|
|
|
|
|
imgs_to_preload.push(this.image_url);
|
|
|
|
|
this.img_html = '<img id="img" src="' + this.image_url + '"/>';
|
|
|
|
|
} else {
|
|
|
|
|
this.img_html = '';
|
|
|
|
|
}
|
|
|
|
|
if (this.thumb_image_url) {
|
|
|
|
|
imgs_to_preload.push(this.thumb_image_url);
|
|
|
|
|
this.thumb_img_html =
|
|
|
|
|
'<div id="thumbborder-wrapper"><div id="thumbborder"></div></div>' +
|
|
|
|
|
'<img id="thumbnail"' +
|
|
|
|
|
' src="' + this.thumb_image_url + '"' +
|
|
|
|
|
' width="' + MPNotif.THUMB_IMG_SIZE + '"' +
|
|
|
|
|
' height="' + MPNotif.THUMB_IMG_SIZE + '"' +
|
|
|
|
|
'/>' +
|
|
|
|
|
'<div id="thumbspacer"></div>';
|
|
|
|
|
} else {
|
|
|
|
|
this.thumb_img_html = '';
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this.thumb_image_url = this.thumb_image_url || '//cdn.mxpnl.com/site_media/images/icons/notifications/mini-news-dark.png';
|
|
|
|
|
imgs_to_preload.push(this.thumb_image_url);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return imgs_to_preload;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._init_notification_el = function() {
|
|
|
|
|
var notification_html = '';
|
|
|
|
|
var video_src = '';
|
|
|
|
|
var video_html = '';
|
|
|
|
|
var cancel_html = '<div id="cancel">' +
|
|
|
|
|
'<div id="cancel-icon"></div>' +
|
|
|
|
|
'</div>';
|
|
|
|
|
|
|
|
|
|
this.notification_el = document$1.createElement('div');
|
|
|
|
|
this.notification_el.id = MPNotif.MARKUP_PREFIX + '-wrapper';
|
|
|
|
|
if (!this.mini) {
|
|
|
|
|
// TAKEOVER notification
|
|
|
|
|
var close_html = (this.clickthrough || this.show_video) ? '' : '<div id="button-close"></div>',
|
|
|
|
|
play_html = this.show_video ? '<div id="button-play"></div>' : '';
|
|
|
|
|
if (this._browser_lte('ie', 7)) {
|
|
|
|
|
close_html = '';
|
|
|
|
|
play_html = '';
|
|
|
|
|
}
|
|
|
|
|
notification_html =
|
|
|
|
|
'<div id="takeover">' +
|
|
|
|
|
this.thumb_img_html +
|
|
|
|
|
'<div id="mainbox">' +
|
|
|
|
|
cancel_html +
|
|
|
|
|
'<div id="content">' +
|
|
|
|
|
this.img_html +
|
|
|
|
|
'<div id="title">' + this.title + '</div>' +
|
|
|
|
|
'<div id="body">' + this.body + '</div>' +
|
|
|
|
|
'<div id="tagline">' +
|
|
|
|
|
'<a href="http://mixpanel.com?from=inapp" target="_blank">POWERED BY MIXPANEL</a>' +
|
|
|
|
|
'</div>' +
|
|
|
|
|
'</div>' +
|
|
|
|
|
'<div id="button">' +
|
|
|
|
|
close_html +
|
|
|
|
|
'<a id="button-link" href="' + this.dest_url + '">' + this.cta + '</a>' +
|
|
|
|
|
play_html +
|
|
|
|
|
'</div>' +
|
|
|
|
|
'</div>' +
|
|
|
|
|
'</div>';
|
|
|
|
|
} else {
|
|
|
|
|
// MINI notification
|
|
|
|
|
notification_html =
|
|
|
|
|
'<div id="mini">' +
|
|
|
|
|
'<div id="mainbox">' +
|
|
|
|
|
cancel_html +
|
|
|
|
|
'<div id="mini-content">' +
|
|
|
|
|
'<div id="mini-icon">' +
|
|
|
|
|
'<div id="mini-icon-img"></div>' +
|
|
|
|
|
'</div>' +
|
|
|
|
|
'<div id="body">' +
|
|
|
|
|
'<div id="body-text"><div>' + this.body + '</div></div>' +
|
|
|
|
|
'</div>' +
|
|
|
|
|
'</div>' +
|
|
|
|
|
'</div>' +
|
|
|
|
|
'<div id="mini-border"></div>' +
|
|
|
|
|
'</div>';
|
|
|
|
|
}
|
|
|
|
|
if (this.youtube_video) {
|
|
|
|
|
video_src = '//www.youtube.com/embed/' + this.youtube_video +
|
|
|
|
|
'?wmode=transparent&showinfo=0&modestbranding=0&rel=0&autoplay=1&loop=0&vq=hd1080';
|
|
|
|
|
if (this.yt_custom) {
|
|
|
|
|
video_src += '&enablejsapi=1&html5=1&controls=0';
|
|
|
|
|
video_html =
|
|
|
|
|
'<div id="video-controls">' +
|
|
|
|
|
'<div id="video-progress" class="video-progress-el">' +
|
|
|
|
|
'<div id="video-progress-total" class="video-progress-el"></div>' +
|
|
|
|
|
'<div id="video-elapsed" class="video-progress-el"></div>' +
|
|
|
|
|
'</div>' +
|
|
|
|
|
'<div id="video-time" class="video-progress-el"></div>' +
|
|
|
|
|
'</div>';
|
|
|
|
|
}
|
|
|
|
|
} else if (this.vimeo_video) {
|
|
|
|
|
video_src = '//player.vimeo.com/video/' + this.vimeo_video + '?autoplay=1&title=0&byline=0&portrait=0';
|
|
|
|
|
}
|
|
|
|
|
if (this.show_video) {
|
|
|
|
|
this.video_iframe =
|
|
|
|
|
'<iframe id="' + MPNotif.MARKUP_PREFIX + '-video-frame" ' +
|
|
|
|
|
'width="' + this.video_width + '" height="' + this.video_height + '" ' +
|
|
|
|
|
' src="' + video_src + '"' +
|
|
|
|
|
' frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen="1" scrolling="no"' +
|
|
|
|
|
'></iframe>';
|
|
|
|
|
video_html =
|
|
|
|
|
'<div id="video-' + (this.flip_animate ? '' : 'no') + 'flip">' +
|
|
|
|
|
'<div id="video">' +
|
|
|
|
|
'<div id="video-holder"></div>' +
|
|
|
|
|
video_html +
|
|
|
|
|
'</div>' +
|
|
|
|
|
'</div>';
|
|
|
|
|
}
|
|
|
|
|
var main_html = video_html + notification_html;
|
|
|
|
|
if (this.flip_animate) {
|
|
|
|
|
main_html =
|
|
|
|
|
(this.mini ? notification_html : '') +
|
|
|
|
|
'<div id="flipcontainer"><div id="flipper">' +
|
|
|
|
|
(this.mini ? video_html : main_html) +
|
|
|
|
|
'</div></div>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.notification_el.innerHTML =
|
|
|
|
|
('<div id="overlay" class="' + this.notif_type + '">' +
|
|
|
|
|
'<div id="campaignid-' + this.campaign_id + '">' +
|
|
|
|
|
'<div id="bgwrapper">' +
|
|
|
|
|
'<div id="bg"></div>' +
|
|
|
|
|
main_html +
|
|
|
|
|
'</div>' +
|
|
|
|
|
'</div>' +
|
|
|
|
|
'</div>')
|
|
|
|
|
.replace(/class=\"/g, 'class="' + MPNotif.MARKUP_PREFIX + '-')
|
|
|
|
|
.replace(/id=\"/g, 'id="' + MPNotif.MARKUP_PREFIX + '-');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._init_styles = function() {
|
|
|
|
|
if (this.style === 'dark') {
|
|
|
|
|
this.style_vals = {
|
|
|
|
|
bg: '#1d1f25',
|
|
|
|
|
bg_actions: '#282b32',
|
|
|
|
|
bg_hover: '#3a4147',
|
|
|
|
|
bg_light: '#4a5157',
|
|
|
|
|
border_gray: '#32353c',
|
|
|
|
|
cancel_opacity: '0.4',
|
|
|
|
|
mini_hover: '#2a3137',
|
|
|
|
|
text_title: '#fff',
|
|
|
|
|
text_main: '#9498a3',
|
|
|
|
|
text_tagline: '#464851',
|
|
|
|
|
text_hover: '#ddd'
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
this.style_vals = {
|
|
|
|
|
bg: '#fff',
|
|
|
|
|
bg_actions: '#e7eaee',
|
|
|
|
|
bg_hover: '#eceff3',
|
|
|
|
|
bg_light: '#f5f5f5',
|
|
|
|
|
border_gray: '#e4ecf2',
|
|
|
|
|
cancel_opacity: '1.0',
|
|
|
|
|
mini_hover: '#fafafa',
|
|
|
|
|
text_title: '#5c6578',
|
|
|
|
|
text_main: '#8b949b',
|
|
|
|
|
text_tagline: '#ced9e6',
|
|
|
|
|
text_hover: '#7c8598'
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
var shadow = '0px 0px 35px 0px rgba(45, 49, 56, 0.7)',
|
|
|
|
|
video_shadow = shadow,
|
|
|
|
|
mini_shadow = shadow,
|
|
|
|
|
thumb_total_size = MPNotif.THUMB_IMG_SIZE + MPNotif.THUMB_BORDER_SIZE * 2,
|
|
|
|
|
anim_seconds = (MPNotif.ANIM_TIME / 1000) + 's';
|
|
|
|
|
if (this.mini) {
|
|
|
|
|
shadow = 'none';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// don't display on small viewports
|
|
|
|
|
var notif_media_queries = {},
|
|
|
|
|
min_width = MPNotif.NOTIF_WIDTH_MINI + 20;
|
|
|
|
|
notif_media_queries['@media only screen and (max-width: ' + (min_width - 1) + 'px)'] = {
|
|
|
|
|
'#overlay': {
|
|
|
|
|
'display': 'none'
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var notif_styles = {
|
|
|
|
|
'.flipped': {
|
|
|
|
|
'transform': 'rotateY(180deg)'
|
|
|
|
|
},
|
|
|
|
|
'#overlay': {
|
|
|
|
|
'position': 'fixed',
|
|
|
|
|
'top': '0',
|
|
|
|
|
'left': '0',
|
|
|
|
|
'width': '100%',
|
|
|
|
|
'height': '100%',
|
|
|
|
|
'overflow': 'auto',
|
|
|
|
|
'text-align': 'center',
|
|
|
|
|
'z-index': '10000',
|
|
|
|
|
'font-family': '"Helvetica", "Arial", sans-serif',
|
|
|
|
|
'-webkit-font-smoothing': 'antialiased',
|
|
|
|
|
'-moz-osx-font-smoothing': 'grayscale'
|
|
|
|
|
},
|
|
|
|
|
'#overlay.mini': {
|
|
|
|
|
'height': '0',
|
|
|
|
|
'overflow': 'visible'
|
|
|
|
|
},
|
|
|
|
|
'#overlay a': {
|
|
|
|
|
'width': 'initial',
|
|
|
|
|
'padding': '0',
|
|
|
|
|
'text-decoration': 'none',
|
|
|
|
|
'text-transform': 'none',
|
|
|
|
|
'color': 'inherit'
|
|
|
|
|
},
|
|
|
|
|
'#bgwrapper': {
|
|
|
|
|
'position': 'relative',
|
|
|
|
|
'width': '100%',
|
|
|
|
|
'height': '100%'
|
|
|
|
|
},
|
|
|
|
|
'#bg': {
|
|
|
|
|
'position': 'fixed',
|
|
|
|
|
'top': '0',
|
|
|
|
|
'left': '0',
|
|
|
|
|
'width': '100%',
|
|
|
|
|
'height': '100%',
|
|
|
|
|
'min-width': this.doc_width * 4 + 'px',
|
|
|
|
|
'min-height': this.doc_height * 4 + 'px',
|
|
|
|
|
'background-color': 'black',
|
|
|
|
|
'opacity': '0.0',
|
|
|
|
|
'-ms-filter': 'progid:DXImageTransform.Microsoft.Alpha(Opacity=60)', // IE8
|
|
|
|
|
'filter': 'alpha(opacity=60)', // IE5-7
|
|
|
|
|
'transition': 'opacity ' + anim_seconds
|
|
|
|
|
},
|
|
|
|
|
'#bg.visible': {
|
|
|
|
|
'opacity': MPNotif.BG_OPACITY
|
|
|
|
|
},
|
|
|
|
|
'.mini #bg': {
|
|
|
|
|
'width': '0',
|
|
|
|
|
'height': '0',
|
|
|
|
|
'min-width': '0'
|
|
|
|
|
},
|
|
|
|
|
'#flipcontainer': {
|
|
|
|
|
'perspective': '1000px',
|
|
|
|
|
'position': 'absolute',
|
|
|
|
|
'width': '100%'
|
|
|
|
|
},
|
|
|
|
|
'#flipper': {
|
|
|
|
|
'position': 'relative',
|
|
|
|
|
'transform-style': 'preserve-3d',
|
|
|
|
|
'transition': '0.3s'
|
|
|
|
|
},
|
|
|
|
|
'#takeover': {
|
|
|
|
|
'position': 'absolute',
|
|
|
|
|
'left': '50%',
|
|
|
|
|
'width': MPNotif.NOTIF_WIDTH + 'px',
|
|
|
|
|
'margin-left': Math.round(-MPNotif.NOTIF_WIDTH / 2) + 'px',
|
|
|
|
|
'backface-visibility': 'hidden',
|
|
|
|
|
'transform': 'rotateY(0deg)',
|
|
|
|
|
'opacity': '0.0',
|
|
|
|
|
'top': MPNotif.NOTIF_START_TOP + 'px',
|
|
|
|
|
'transition': 'opacity ' + anim_seconds + ', top ' + anim_seconds
|
|
|
|
|
},
|
|
|
|
|
'#takeover.visible': {
|
|
|
|
|
'opacity': '1.0',
|
|
|
|
|
'top': MPNotif.NOTIF_TOP + 'px'
|
|
|
|
|
},
|
|
|
|
|
'#takeover.exiting': {
|
|
|
|
|
'opacity': '0.0',
|
|
|
|
|
'top': MPNotif.NOTIF_START_TOP + 'px'
|
|
|
|
|
},
|
|
|
|
|
'#thumbspacer': {
|
|
|
|
|
'height': MPNotif.THUMB_OFFSET + 'px'
|
|
|
|
|
},
|
|
|
|
|
'#thumbborder-wrapper': {
|
|
|
|
|
'position': 'absolute',
|
|
|
|
|
'top': (-MPNotif.THUMB_BORDER_SIZE) + 'px',
|
|
|
|
|
'left': (MPNotif.NOTIF_WIDTH / 2 - MPNotif.THUMB_OFFSET - MPNotif.THUMB_BORDER_SIZE) + 'px',
|
|
|
|
|
'width': thumb_total_size + 'px',
|
|
|
|
|
'height': (thumb_total_size / 2) + 'px',
|
|
|
|
|
'overflow': 'hidden'
|
|
|
|
|
},
|
|
|
|
|
'#thumbborder': {
|
|
|
|
|
'position': 'absolute',
|
|
|
|
|
'width': thumb_total_size + 'px',
|
|
|
|
|
'height': thumb_total_size + 'px',
|
|
|
|
|
'border-radius': thumb_total_size + 'px',
|
|
|
|
|
'background-color': this.style_vals.bg_actions,
|
|
|
|
|
'opacity': '0.5'
|
|
|
|
|
},
|
|
|
|
|
'#thumbnail': {
|
|
|
|
|
'position': 'absolute',
|
|
|
|
|
'top': '0px',
|
|
|
|
|
'left': (MPNotif.NOTIF_WIDTH / 2 - MPNotif.THUMB_OFFSET) + 'px',
|
|
|
|
|
'width': MPNotif.THUMB_IMG_SIZE + 'px',
|
|
|
|
|
'height': MPNotif.THUMB_IMG_SIZE + 'px',
|
|
|
|
|
'overflow': 'hidden',
|
|
|
|
|
'z-index': '100',
|
|
|
|
|
'border-radius': MPNotif.THUMB_IMG_SIZE + 'px'
|
|
|
|
|
},
|
|
|
|
|
'#mini': {
|
|
|
|
|
'position': 'absolute',
|
|
|
|
|
'right': '20px',
|
|
|
|
|
'top': MPNotif.NOTIF_TOP + 'px',
|
|
|
|
|
'width': this.notif_width + 'px',
|
|
|
|
|
'height': MPNotif.NOTIF_HEIGHT_MINI * 2 + 'px',
|
|
|
|
|
'margin-top': 20 - MPNotif.NOTIF_HEIGHT_MINI + 'px',
|
|
|
|
|
'backface-visibility': 'hidden',
|
|
|
|
|
'opacity': '0.0',
|
|
|
|
|
'transform': 'rotateX(90deg)',
|
|
|
|
|
'transition': 'opacity 0.3s, transform 0.3s, right 0.3s'
|
|
|
|
|
},
|
|
|
|
|
'#mini.visible': {
|
|
|
|
|
'opacity': '1.0',
|
|
|
|
|
'transform': 'rotateX(0deg)'
|
|
|
|
|
},
|
|
|
|
|
'#mini.exiting': {
|
|
|
|
|
'opacity': '0.0',
|
|
|
|
|
'right': '-150px'
|
|
|
|
|
},
|
|
|
|
|
'#mainbox': {
|
|
|
|
|
'border-radius': '4px',
|
|
|
|
|
'box-shadow': shadow,
|
|
|
|
|
'text-align': 'center',
|
|
|
|
|
'background-color': this.style_vals.bg,
|
|
|
|
|
'font-size': '14px',
|
|
|
|
|
'color': this.style_vals.text_main
|
|
|
|
|
},
|
|
|
|
|
'#mini #mainbox': {
|
|
|
|
|
'height': MPNotif.NOTIF_HEIGHT_MINI + 'px',
|
|
|
|
|
'margin-top': MPNotif.NOTIF_HEIGHT_MINI + 'px',
|
|
|
|
|
'border-radius': '3px',
|
|
|
|
|
'transition': 'background-color ' + anim_seconds
|
|
|
|
|
},
|
|
|
|
|
'#mini-border': {
|
|
|
|
|
'height': (MPNotif.NOTIF_HEIGHT_MINI + 6) + 'px',
|
|
|
|
|
'width': (MPNotif.NOTIF_WIDTH_MINI + 6) + 'px',
|
|
|
|
|
'position': 'absolute',
|
|
|
|
|
'top': '-3px',
|
|
|
|
|
'left': '-3px',
|
|
|
|
|
'margin-top': MPNotif.NOTIF_HEIGHT_MINI + 'px',
|
|
|
|
|
'border-radius': '6px',
|
|
|
|
|
'opacity': '0.25',
|
|
|
|
|
'background-color': '#fff',
|
|
|
|
|
'z-index': '-1',
|
|
|
|
|
'box-shadow': mini_shadow
|
|
|
|
|
},
|
|
|
|
|
'#mini-icon': {
|
|
|
|
|
'position': 'relative',
|
|
|
|
|
'display': 'inline-block',
|
|
|
|
|
'width': '75px',
|
|
|
|
|
'height': MPNotif.NOTIF_HEIGHT_MINI + 'px',
|
|
|
|
|
'border-radius': '3px 0 0 3px',
|
|
|
|
|
'background-color': this.style_vals.bg_actions,
|
|
|
|
|
'background': 'linear-gradient(135deg, ' + this.style_vals.bg_light + ' 0%, ' + this.style_vals.bg_actions + ' 100%)',
|
|
|
|
|
'transition': 'background-color ' + anim_seconds
|
|
|
|
|
},
|
|
|
|
|
'#mini:hover #mini-icon': {
|
|
|
|
|
'background-color': this.style_vals.mini_hover
|
|
|
|
|
},
|
|
|
|
|
'#mini:hover #mainbox': {
|
|
|
|
|
'background-color': this.style_vals.mini_hover
|
|
|
|
|
},
|
|
|
|
|
'#mini-icon-img': {
|
|
|
|
|
'position': 'absolute',
|
|
|
|
|
'background-image': 'url(' + this.thumb_image_url + ')',
|
|
|
|
|
'width': '48px',
|
|
|
|
|
'height': '48px',
|
|
|
|
|
'top': '20px',
|
|
|
|
|
'left': '12px'
|
|
|
|
|
},
|
|
|
|
|
'#content': {
|
|
|
|
|
'padding': '30px 20px 0px 20px'
|
|
|
|
|
},
|
|
|
|
|
'#mini-content': {
|
|
|
|
|
'text-align': 'left',
|
|
|
|
|
'height': MPNotif.NOTIF_HEIGHT_MINI + 'px',
|
|
|
|
|
'cursor': 'pointer'
|
|
|
|
|
},
|
|
|
|
|
'#img': {
|
|
|
|
|
'width': '328px',
|
|
|
|
|
'margin-top': '30px',
|
|
|
|
|
'border-radius': '5px'
|
|
|
|
|
},
|
|
|
|
|
'#title': {
|
|
|
|
|
'max-height': '600px',
|
|
|
|
|
'overflow': 'hidden',
|
|
|
|
|
'word-wrap': 'break-word',
|
|
|
|
|
'padding': '25px 0px 20px 0px',
|
|
|
|
|
'font-size': '19px',
|
|
|
|
|
'font-weight': 'bold',
|
|
|
|
|
'color': this.style_vals.text_title
|
|
|
|
|
},
|
|
|
|
|
'#body': {
|
|
|
|
|
'max-height': '600px',
|
|
|
|
|
'margin-bottom': '25px',
|
|
|
|
|
'overflow': 'hidden',
|
|
|
|
|
'word-wrap': 'break-word',
|
|
|
|
|
'line-height': '21px',
|
|
|
|
|
'font-size': '15px',
|
|
|
|
|
'font-weight': 'normal',
|
|
|
|
|
'text-align': 'left'
|
|
|
|
|
},
|
|
|
|
|
'#mini #body': {
|
|
|
|
|
'display': 'inline-block',
|
|
|
|
|
'max-width': '250px',
|
|
|
|
|
'margin': '0 0 0 30px',
|
|
|
|
|
'height': MPNotif.NOTIF_HEIGHT_MINI + 'px',
|
|
|
|
|
'font-size': '16px',
|
|
|
|
|
'letter-spacing': '0.8px',
|
|
|
|
|
'color': this.style_vals.text_title
|
|
|
|
|
},
|
|
|
|
|
'#mini #body-text': {
|
|
|
|
|
'display': 'table',
|
|
|
|
|
'height': MPNotif.NOTIF_HEIGHT_MINI + 'px'
|
|
|
|
|
},
|
|
|
|
|
'#mini #body-text div': {
|
|
|
|
|
'display': 'table-cell',
|
|
|
|
|
'vertical-align': 'middle'
|
|
|
|
|
},
|
|
|
|
|
'#tagline': {
|
|
|
|
|
'margin-bottom': '15px',
|
|
|
|
|
'font-size': '10px',
|
|
|
|
|
'font-weight': '600',
|
|
|
|
|
'letter-spacing': '0.8px',
|
|
|
|
|
'color': '#ccd7e0',
|
|
|
|
|
'text-align': 'left'
|
|
|
|
|
},
|
|
|
|
|
'#tagline a': {
|
|
|
|
|
'color': this.style_vals.text_tagline,
|
|
|
|
|
'transition': 'color ' + anim_seconds
|
|
|
|
|
},
|
|
|
|
|
'#tagline a:hover': {
|
|
|
|
|
'color': this.style_vals.text_hover
|
|
|
|
|
},
|
|
|
|
|
'#cancel': {
|
|
|
|
|
'position': 'absolute',
|
|
|
|
|
'right': '0',
|
|
|
|
|
'width': '8px',
|
|
|
|
|
'height': '8px',
|
|
|
|
|
'padding': '10px',
|
|
|
|
|
'border-radius': '20px',
|
|
|
|
|
'margin': '12px 12px 0 0',
|
|
|
|
|
'box-sizing': 'content-box',
|
|
|
|
|
'cursor': 'pointer',
|
|
|
|
|
'transition': 'background-color ' + anim_seconds
|
|
|
|
|
},
|
|
|
|
|
'#mini #cancel': {
|
|
|
|
|
'margin': '7px 7px 0 0'
|
|
|
|
|
},
|
|
|
|
|
'#cancel-icon': {
|
|
|
|
|
'width': '8px',
|
|
|
|
|
'height': '8px',
|
|
|
|
|
'overflow': 'hidden',
|
|
|
|
|
'background-image': 'url(//cdn.mxpnl.com/site_media/images/icons/notifications/cancel-x.png)',
|
|
|
|
|
'opacity': this.style_vals.cancel_opacity
|
|
|
|
|
},
|
|
|
|
|
'#cancel:hover': {
|
|
|
|
|
'background-color': this.style_vals.bg_hover
|
|
|
|
|
},
|
|
|
|
|
'#button': {
|
|
|
|
|
'display': 'block',
|
|
|
|
|
'height': '60px',
|
|
|
|
|
'line-height': '60px',
|
|
|
|
|
'text-align': 'center',
|
|
|
|
|
'background-color': this.style_vals.bg_actions,
|
|
|
|
|
'border-radius': '0 0 4px 4px',
|
|
|
|
|
'overflow': 'hidden',
|
|
|
|
|
'cursor': 'pointer',
|
|
|
|
|
'transition': 'background-color ' + anim_seconds
|
|
|
|
|
},
|
|
|
|
|
'#button-close': {
|
|
|
|
|
'display': 'inline-block',
|
|
|
|
|
'width': '9px',
|
|
|
|
|
'height': '60px',
|
|
|
|
|
'margin-right': '8px',
|
|
|
|
|
'vertical-align': 'top',
|
|
|
|
|
'background-image': 'url(//cdn.mxpnl.com/site_media/images/icons/notifications/close-x-' + this.style + '.png)',
|
|
|
|
|
'background-repeat': 'no-repeat',
|
|
|
|
|
'background-position': '0px 25px'
|
|
|
|
|
},
|
|
|
|
|
'#button-play': {
|
|
|
|
|
'display': 'inline-block',
|
|
|
|
|
'width': '30px',
|
|
|
|
|
'height': '60px',
|
|
|
|
|
'margin-left': '15px',
|
|
|
|
|
'background-image': 'url(//cdn.mxpnl.com/site_media/images/icons/notifications/play-' + this.style + '-small.png)',
|
|
|
|
|
'background-repeat': 'no-repeat',
|
|
|
|
|
'background-position': '0px 15px'
|
|
|
|
|
},
|
|
|
|
|
'a#button-link': {
|
|
|
|
|
'display': 'inline-block',
|
|
|
|
|
'vertical-align': 'top',
|
|
|
|
|
'text-align': 'center',
|
|
|
|
|
'font-size': '17px',
|
|
|
|
|
'font-weight': 'bold',
|
|
|
|
|
'overflow': 'hidden',
|
|
|
|
|
'word-wrap': 'break-word',
|
|
|
|
|
'color': this.style_vals.text_title,
|
|
|
|
|
'transition': 'color ' + anim_seconds
|
|
|
|
|
},
|
|
|
|
|
'#button:hover': {
|
|
|
|
|
'background-color': this.style_vals.bg_hover,
|
|
|
|
|
'color': this.style_vals.text_hover
|
|
|
|
|
},
|
|
|
|
|
'#button:hover a': {
|
|
|
|
|
'color': this.style_vals.text_hover
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
'#video-noflip': {
|
|
|
|
|
'position': 'relative',
|
|
|
|
|
'top': (-this.video_height * 2) + 'px'
|
|
|
|
|
},
|
|
|
|
|
'#video-flip': {
|
|
|
|
|
'backface-visibility': 'hidden',
|
|
|
|
|
'transform': 'rotateY(180deg)'
|
|
|
|
|
},
|
|
|
|
|
'#video': {
|
|
|
|
|
'position': 'absolute',
|
|
|
|
|
'width': (this.video_width - 1) + 'px',
|
|
|
|
|
'height': this.video_height + 'px',
|
|
|
|
|
'top': MPNotif.NOTIF_TOP + 'px',
|
|
|
|
|
'margin-top': '100px',
|
|
|
|
|
'left': '50%',
|
|
|
|
|
'margin-left': Math.round(-this.video_width / 2) + 'px',
|
|
|
|
|
'overflow': 'hidden',
|
|
|
|
|
'border-radius': '5px',
|
|
|
|
|
'box-shadow': video_shadow,
|
|
|
|
|
'transform': 'translateZ(1px)', // webkit rendering bug http://stackoverflow.com/questions/18167981/clickable-link-area-unexpectedly-smaller-after-css-transform
|
|
|
|
|
'transition': 'opacity ' + anim_seconds + ', top ' + anim_seconds
|
|
|
|
|
},
|
|
|
|
|
'#video.exiting': {
|
|
|
|
|
'opacity': '0.0',
|
|
|
|
|
'top': this.video_height + 'px'
|
|
|
|
|
},
|
|
|
|
|
'#video-holder': {
|
|
|
|
|
'position': 'absolute',
|
|
|
|
|
'width': (this.video_width - 1) + 'px',
|
|
|
|
|
'height': this.video_height + 'px',
|
|
|
|
|
'overflow': 'hidden',
|
|
|
|
|
'border-radius': '5px'
|
|
|
|
|
},
|
|
|
|
|
'#video-frame': {
|
|
|
|
|
'margin-left': '-1px',
|
|
|
|
|
'width': this.video_width + 'px'
|
|
|
|
|
},
|
|
|
|
|
'#video-controls': {
|
|
|
|
|
'opacity': '0',
|
|
|
|
|
'transition': 'opacity 0.5s'
|
|
|
|
|
},
|
|
|
|
|
'#video:hover #video-controls': {
|
|
|
|
|
'opacity': '1.0'
|
|
|
|
|
},
|
|
|
|
|
'#video .video-progress-el': {
|
|
|
|
|
'position': 'absolute',
|
|
|
|
|
'bottom': '0',
|
|
|
|
|
'height': '25px',
|
|
|
|
|
'border-radius': '0 0 0 5px'
|
|
|
|
|
},
|
|
|
|
|
'#video-progress': {
|
|
|
|
|
'width': '90%'
|
|
|
|
|
},
|
|
|
|
|
'#video-progress-total': {
|
|
|
|
|
'width': '100%',
|
|
|
|
|
'background-color': this.style_vals.bg,
|
|
|
|
|
'opacity': '0.7'
|
|
|
|
|
},
|
|
|
|
|
'#video-elapsed': {
|
|
|
|
|
'width': '0',
|
|
|
|
|
'background-color': '#6cb6f5',
|
|
|
|
|
'opacity': '0.9'
|
|
|
|
|
},
|
|
|
|
|
'#video #video-time': {
|
|
|
|
|
'width': '10%',
|
|
|
|
|
'right': '0',
|
|
|
|
|
'font-size': '11px',
|
|
|
|
|
'line-height': '25px',
|
|
|
|
|
'color': this.style_vals.text_main,
|
|
|
|
|
'background-color': '#666',
|
|
|
|
|
'border-radius': '0 0 5px 0'
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// IE hacks
|
|
|
|
|
if (this._browser_lte('ie', 8)) {
|
|
|
|
|
_.extend(notif_styles, {
|
|
|
|
|
'* html #overlay': {
|
|
|
|
|
'position': 'absolute'
|
|
|
|
|
},
|
|
|
|
|
'* html #bg': {
|
|
|
|
|
'position': 'absolute'
|
|
|
|
|
},
|
|
|
|
|
'html, body': {
|
|
|
|
|
'height': '100%'
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (this._browser_lte('ie', 7)) {
|
|
|
|
|
_.extend(notif_styles, {
|
|
|
|
|
'#mini #body': {
|
|
|
|
|
'display': 'inline',
|
|
|
|
|
'zoom': '1',
|
|
|
|
|
'border': '1px solid ' + this.style_vals.bg_hover
|
|
|
|
|
},
|
|
|
|
|
'#mini #body-text': {
|
|
|
|
|
'padding': '20px'
|
|
|
|
|
},
|
|
|
|
|
'#mini #mini-icon': {
|
|
|
|
|
'display': 'none'
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// add vendor-prefixed style rules
|
|
|
|
|
var VENDOR_STYLES = ['backface-visibility', 'border-radius', 'box-shadow', 'opacity',
|
|
|
|
|
'perspective', 'transform', 'transform-style', 'transition'],
|
|
|
|
|
VENDOR_PREFIXES = ['khtml', 'moz', 'ms', 'o', 'webkit'];
|
|
|
|
|
for (var selector in notif_styles) {
|
|
|
|
|
for (var si = 0; si < VENDOR_STYLES.length; si++) {
|
|
|
|
|
var prop = VENDOR_STYLES[si];
|
|
|
|
|
if (prop in notif_styles[selector]) {
|
|
|
|
|
var val = notif_styles[selector][prop];
|
|
|
|
|
for (var pi = 0; pi < VENDOR_PREFIXES.length; pi++) {
|
|
|
|
|
notif_styles[selector]['-' + VENDOR_PREFIXES[pi] + '-' + prop] = val;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var inject_styles = function(styles, media_queries) {
|
|
|
|
|
var create_style_text = function(style_defs) {
|
|
|
|
|
var st = '';
|
|
|
|
|
for (var selector in style_defs) {
|
|
|
|
|
var mp_selector = selector
|
|
|
|
|
.replace(/#/g, '#' + MPNotif.MARKUP_PREFIX + '-')
|
|
|
|
|
.replace(/\./g, '.' + MPNotif.MARKUP_PREFIX + '-');
|
|
|
|
|
st += '\n' + mp_selector + ' {';
|
|
|
|
|
var props = style_defs[selector];
|
|
|
|
|
for (var k in props) {
|
|
|
|
|
st += k + ':' + props[k] + ';';
|
|
|
|
|
}
|
|
|
|
|
st += '}';
|
|
|
|
|
}
|
|
|
|
|
return st;
|
|
|
|
|
};
|
|
|
|
|
var create_media_query_text = function(mq_defs) {
|
|
|
|
|
var mqt = '';
|
|
|
|
|
for (var mq in mq_defs) {
|
|
|
|
|
mqt += '\n' + mq + ' {' + create_style_text(mq_defs[mq]) + '\n}';
|
|
|
|
|
}
|
|
|
|
|
return mqt;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var style_text = create_style_text(styles) + create_media_query_text(media_queries),
|
|
|
|
|
head_el = document$1.head || document$1.getElementsByTagName('head')[0] || document$1.documentElement,
|
|
|
|
|
style_el = document$1.createElement('style');
|
|
|
|
|
head_el.appendChild(style_el);
|
|
|
|
|
style_el.setAttribute('type', 'text/css');
|
|
|
|
|
if (style_el.styleSheet) { // IE
|
|
|
|
|
style_el.styleSheet.cssText = style_text;
|
|
|
|
|
} else {
|
|
|
|
|
style_el.textContent = style_text;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
inject_styles(notif_styles, notif_media_queries);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._init_video = _.safewrap(function() {
|
|
|
|
|
if (!this.video_url) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
|
|
// Youtube iframe API compatibility
|
|
|
|
|
self.yt_custom = 'postMessage' in window$1;
|
|
|
|
|
|
|
|
|
|
self.dest_url = self.video_url;
|
|
|
|
|
var youtube_match = self.video_url.match(
|
|
|
|
|
// http://stackoverflow.com/questions/2936467/parse-youtube-video-id-using-preg-match
|
|
|
|
|
/(?:youtube(?:-nocookie)?\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/ ]{11})/i
|
|
|
|
|
),
|
|
|
|
|
vimeo_match = self.video_url.match(
|
|
|
|
|
/vimeo\.com\/.*?(\d+)/i
|
|
|
|
|
);
|
|
|
|
|
if (youtube_match) {
|
|
|
|
|
self.show_video = true;
|
|
|
|
|
self.youtube_video = youtube_match[1];
|
|
|
|
|
|
|
|
|
|
if (self.yt_custom) {
|
|
|
|
|
window$1['onYouTubeIframeAPIReady'] = function() {
|
|
|
|
|
if (self._get_el('video-frame')) {
|
|
|
|
|
self._yt_video_ready();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// load Youtube iframe API; see https://developers.google.com/youtube/iframe_api_reference
|
|
|
|
|
var tag = document$1.createElement('script');
|
|
|
|
|
tag.src = '//www.youtube.com/iframe_api';
|
|
|
|
|
var firstScriptTag = document$1.getElementsByTagName('script')[0];
|
|
|
|
|
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
|
|
|
|
|
}
|
|
|
|
|
} else if (vimeo_match) {
|
|
|
|
|
self.show_video = true;
|
|
|
|
|
self.vimeo_video = vimeo_match[1];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IE <= 7, FF <= 3: fall through to video link rather than embedded player
|
|
|
|
|
if (self._browser_lte('ie', 7) || self._browser_lte('firefox', 3)) {
|
|
|
|
|
self.show_video = false;
|
|
|
|
|
self.clickthrough = true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._mark_as_shown = _.safewrap(function() {
|
|
|
|
|
// click on background to dismiss
|
|
|
|
|
var self = this;
|
|
|
|
|
_.register_event(self._get_el('bg'), 'click', function() {
|
|
|
|
|
self.dismiss();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var get_style = function(el, style_name) {
|
|
|
|
|
var styles = {};
|
|
|
|
|
if (document$1.defaultView && document$1.defaultView.getComputedStyle) {
|
|
|
|
|
styles = document$1.defaultView.getComputedStyle(el, null); // FF3 requires both args
|
|
|
|
|
} else if (el.currentStyle) { // IE
|
|
|
|
|
styles = el.currentStyle;
|
|
|
|
|
}
|
|
|
|
|
return styles[style_name];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (this.campaign_id) {
|
|
|
|
|
var notif_el = this._get_el('overlay');
|
|
|
|
|
if (notif_el && get_style(notif_el, 'visibility') !== 'hidden' && get_style(notif_el, 'display') !== 'none') {
|
|
|
|
|
this._mark_delivery();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._mark_delivery = _.safewrap(function(extra_props) {
|
|
|
|
|
if (!this.marked_as_shown) {
|
|
|
|
|
this.marked_as_shown = true;
|
|
|
|
|
|
|
|
|
|
if (this.campaign_id) {
|
|
|
|
|
// mark notification shown (local cache)
|
|
|
|
|
this._get_shown_campaigns()[this.campaign_id] = 1 * new Date();
|
|
|
|
|
this.persistence.save();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// track delivery
|
|
|
|
|
this._track_event('$campaign_delivery', extra_props);
|
|
|
|
|
|
|
|
|
|
// mark notification shown (mixpanel property)
|
|
|
|
|
this.mixpanel['people']['append']({
|
|
|
|
|
'$campaigns': this.campaign_id,
|
|
|
|
|
'$notifications': {
|
|
|
|
|
'campaign_id': this.campaign_id,
|
|
|
|
|
'message_id': this.message_id,
|
|
|
|
|
'type': 'web',
|
|
|
|
|
'time': new Date()
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._preload_images = function(all_loaded_cb) {
|
|
|
|
|
var self = this;
|
|
|
|
|
if (this.imgs_to_preload.length === 0) {
|
|
|
|
|
all_loaded_cb();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var preloaded_imgs = 0;
|
|
|
|
|
var img_objs = [];
|
|
|
|
|
var onload = function() {
|
|
|
|
|
preloaded_imgs++;
|
|
|
|
|
if (preloaded_imgs === self.imgs_to_preload.length && all_loaded_cb) {
|
|
|
|
|
all_loaded_cb();
|
|
|
|
|
all_loaded_cb = null;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
for (var i = 0; i < this.imgs_to_preload.length; i++) {
|
|
|
|
|
var img = new Image();
|
|
|
|
|
img.onload = onload;
|
|
|
|
|
img.src = this.imgs_to_preload[i];
|
|
|
|
|
if (img.complete) {
|
|
|
|
|
onload();
|
|
|
|
|
}
|
|
|
|
|
img_objs.push(img);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IE6/7 doesn't fire onload reliably
|
|
|
|
|
if (this._browser_lte('ie', 7)) {
|
|
|
|
|
setTimeout(function() {
|
|
|
|
|
var imgs_loaded = true;
|
|
|
|
|
for (i = 0; i < img_objs.length; i++) {
|
|
|
|
|
if (!img_objs[i].complete) {
|
|
|
|
|
imgs_loaded = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (imgs_loaded && all_loaded_cb) {
|
|
|
|
|
all_loaded_cb();
|
|
|
|
|
all_loaded_cb = null;
|
|
|
|
|
}
|
|
|
|
|
}, 500);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._remove_notification_el = _.safewrap(function() {
|
|
|
|
|
window$1.clearInterval(this._video_progress_checker);
|
|
|
|
|
this.notification_el.style.visibility = 'hidden';
|
|
|
|
|
this.body_el.removeChild(this.notification_el);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._set_client_config = function() {
|
|
|
|
|
var get_browser_version = function(browser_ex) {
|
|
|
|
|
var match = navigator.userAgent.match(browser_ex);
|
|
|
|
|
return match && match[1];
|
|
|
|
|
};
|
|
|
|
|
this.browser_versions = {};
|
|
|
|
|
this.browser_versions['chrome'] = get_browser_version(/Chrome\/(\d+)/);
|
|
|
|
|
this.browser_versions['firefox'] = get_browser_version(/Firefox\/(\d+)/);
|
|
|
|
|
this.browser_versions['ie'] = get_browser_version(/MSIE (\d+).+/);
|
|
|
|
|
if (!this.browser_versions['ie'] && !(window$1.ActiveXObject) && 'ActiveXObject' in window$1) {
|
|
|
|
|
this.browser_versions['ie'] = 11;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.body_el = document$1.body || document$1.getElementsByTagName('body')[0];
|
|
|
|
|
if (this.body_el) {
|
|
|
|
|
this.doc_width = Math.max(
|
|
|
|
|
this.body_el.scrollWidth, document$1.documentElement.scrollWidth,
|
|
|
|
|
this.body_el.offsetWidth, document$1.documentElement.offsetWidth,
|
|
|
|
|
this.body_el.clientWidth, document$1.documentElement.clientWidth
|
|
|
|
|
);
|
|
|
|
|
this.doc_height = Math.max(
|
|
|
|
|
this.body_el.scrollHeight, document$1.documentElement.scrollHeight,
|
|
|
|
|
this.body_el.offsetHeight, document$1.documentElement.offsetHeight,
|
|
|
|
|
this.body_el.clientHeight, document$1.documentElement.clientHeight
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// detect CSS compatibility
|
|
|
|
|
var ie_ver = this.browser_versions['ie'];
|
|
|
|
|
var sample_styles = document$1.createElement('div').style,
|
|
|
|
|
is_css_compatible = function(rule) {
|
|
|
|
|
if (rule in sample_styles) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (!ie_ver) {
|
|
|
|
|
rule = rule[0].toUpperCase() + rule.slice(1);
|
|
|
|
|
var props = ['O' + rule, 'Webkit' + rule, 'Moz' + rule];
|
|
|
|
|
for (var i = 0; i < props.length; i++) {
|
|
|
|
|
if (props[i] in sample_styles) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
this.use_transitions = this.body_el &&
|
|
|
|
|
is_css_compatible('transition') &&
|
|
|
|
|
is_css_compatible('transform');
|
|
|
|
|
this.flip_animate = (this.browser_versions['chrome'] >= 33 || this.browser_versions['firefox'] >= 15) &&
|
|
|
|
|
this.body_el &&
|
|
|
|
|
is_css_compatible('backfaceVisibility') &&
|
|
|
|
|
is_css_compatible('perspective') &&
|
|
|
|
|
is_css_compatible('transform');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._switch_to_video = _.safewrap(function() {
|
|
|
|
|
var self = this,
|
|
|
|
|
anims = [
|
|
|
|
|
{
|
|
|
|
|
el: self._get_notification_display_el(),
|
|
|
|
|
attr: 'opacity',
|
|
|
|
|
start: 1.0,
|
|
|
|
|
goal: 0.0
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
el: self._get_notification_display_el(),
|
|
|
|
|
attr: 'top',
|
|
|
|
|
start: MPNotif.NOTIF_TOP,
|
|
|
|
|
goal: -500
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
el: self._get_el('video-noflip'),
|
|
|
|
|
attr: 'opacity',
|
|
|
|
|
start: 0.0,
|
|
|
|
|
goal: 1.0
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
el: self._get_el('video-noflip'),
|
|
|
|
|
attr: 'top',
|
|
|
|
|
start: -self.video_height * 2,
|
|
|
|
|
goal: 0
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if (self.mini) {
|
|
|
|
|
var bg = self._get_el('bg'),
|
|
|
|
|
overlay = self._get_el('overlay');
|
|
|
|
|
bg.style.width = '100%';
|
|
|
|
|
bg.style.height = '100%';
|
|
|
|
|
overlay.style.width = '100%';
|
|
|
|
|
|
|
|
|
|
self._add_class(self._get_notification_display_el(), 'exiting');
|
|
|
|
|
self._add_class(bg, 'visible');
|
|
|
|
|
|
|
|
|
|
anims.push({
|
|
|
|
|
el: self._get_el('bg'),
|
|
|
|
|
attr: 'opacity',
|
|
|
|
|
start: 0.0,
|
|
|
|
|
goal: MPNotif.BG_OPACITY
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var video_el = self._get_el('video-holder');
|
|
|
|
|
video_el.innerHTML = self.video_iframe;
|
|
|
|
|
|
|
|
|
|
var video_ready = function() {
|
|
|
|
|
if (window$1['YT'] && window$1['YT']['loaded']) {
|
|
|
|
|
self._yt_video_ready();
|
|
|
|
|
}
|
|
|
|
|
self.showing_video = true;
|
|
|
|
|
self._get_notification_display_el().style.visibility = 'hidden';
|
|
|
|
|
};
|
|
|
|
|
if (self.flip_animate) {
|
|
|
|
|
self._add_class('flipper', 'flipped');
|
|
|
|
|
setTimeout(video_ready, MPNotif.ANIM_TIME);
|
|
|
|
|
} else {
|
|
|
|
|
self._animate_els(anims, MPNotif.ANIM_TIME, video_ready);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._track_event = function(event_name, properties, cb) {
|
|
|
|
|
if (this.campaign_id) {
|
|
|
|
|
properties = properties || {};
|
|
|
|
|
properties = _.extend(properties, {
|
|
|
|
|
'campaign_id': this.campaign_id,
|
|
|
|
|
'message_id': this.message_id,
|
|
|
|
|
'message_type': 'web_inapp',
|
|
|
|
|
'message_subtype': this.notif_type
|
|
|
|
|
});
|
|
|
|
|
this.mixpanel['track'](event_name, properties, cb);
|
|
|
|
|
} else if (cb) {
|
|
|
|
|
cb.call();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MPNotif.prototype._yt_video_ready = _.safewrap(function() {
|
|
|
|
|
var self = this;
|
|
|
|
|
if (self.video_inited) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
self.video_inited = true;
|
|
|
|
|
|
|
|
|
|
var progress_bar = self._get_el('video-elapsed'),
|
|
|
|
|
progress_time = self._get_el('video-time'),
|
|
|
|
|
progress_el = self._get_el('video-progress');
|
|
|
|
|
|
|
|
|
|
new window$1['YT']['Player'](MPNotif.MARKUP_PREFIX + '-video-frame', {
|
|
|
|
|
'events': {
|
|
|
|
|
'onReady': function(event) {
|
|
|
|
|
var ytplayer = event['target'],
|
|
|
|
|
video_duration = ytplayer['getDuration'](),
|
|
|
|
|
pad = function(i) {
|
|
|
|
|
return ('00' + i).slice(-2);
|
|
|
|
|
},
|
|
|
|
|
update_video_time = function(current_time) {
|
|
|
|
|
var secs = Math.round(video_duration - current_time),
|
|
|
|
|
mins = Math.floor(secs / 60),
|
|
|
|
|
hours = Math.floor(mins / 60);
|
|
|
|
|
secs -= mins * 60;
|
|
|
|
|
mins -= hours * 60;
|
|
|
|
|
progress_time.innerHTML = '-' + (hours ? hours + ':' : '') + pad(mins) + ':' + pad(secs);
|
|
|
|
|
};
|
|
|
|
|
update_video_time(0);
|
|
|
|
|
self._video_progress_checker = window$1.setInterval(function() {
|
|
|
|
|
var current_time = ytplayer['getCurrentTime']();
|
|
|
|
|
progress_bar.style.width = (current_time / video_duration * 100) + '%';
|
|
|
|
|
update_video_time(current_time);
|
|
|
|
|
}, 250);
|
|
|
|
|
_.register_event(progress_el, 'click', function(e) {
|
|
|
|
|
var clickx = Math.max(0, e.pageX - progress_el.getBoundingClientRect().left);
|
|
|
|
|
ytplayer['seekTo'](video_duration * clickx / progress_el.clientWidth, true);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// EXPORTS (for closure compiler)
|
|
|
|
|
|
|
|
|
|
// MixpanelLib Exports
|
|
|
|
|
MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
|
|
|
|
|
MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
|
|
|
|
|
MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
|
|
|
|
|
MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
|
|
|
|
|
MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
|
|
|
|
|
MixpanelLib.prototype['track_links'] = MixpanelLib.prototype.track_links;
|
|
|
|
|
MixpanelLib.prototype['track_forms'] = MixpanelLib.prototype.track_forms;
|
|
|
|
|
MixpanelLib.prototype['track_pageview'] = MixpanelLib.prototype.track_pageview;
|
|
|
|
|
MixpanelLib.prototype['register'] = MixpanelLib.prototype.register;
|
|
|
|
|
MixpanelLib.prototype['register_once'] = MixpanelLib.prototype.register_once;
|
|
|
|
|
MixpanelLib.prototype['unregister'] = MixpanelLib.prototype.unregister;
|
|
|
|
|
MixpanelLib.prototype['identify'] = MixpanelLib.prototype.identify;
|
|
|
|
|
MixpanelLib.prototype['alias'] = MixpanelLib.prototype.alias;
|
|
|
|
|
MixpanelLib.prototype['name_tag'] = MixpanelLib.prototype.name_tag;
|
|
|
|
|
MixpanelLib.prototype['set_config'] = MixpanelLib.prototype.set_config;
|
|
|
|
|
MixpanelLib.prototype['get_config'] = MixpanelLib.prototype.get_config;
|
|
|
|
|
MixpanelLib.prototype['get_property'] = MixpanelLib.prototype.get_property;
|
|
|
|
|
MixpanelLib.prototype['get_distinct_id'] = MixpanelLib.prototype.get_distinct_id;
|
|
|
|
|
MixpanelLib.prototype['toString'] = MixpanelLib.prototype.toString;
|
|
|
|
|
MixpanelLib.prototype['_check_and_handle_notifications'] = MixpanelLib.prototype._check_and_handle_notifications;
|
|
|
|
|
MixpanelLib.prototype['_show_notification'] = MixpanelLib.prototype._show_notification;
|
|
|
|
|
MixpanelLib.prototype['opt_out_tracking'] = MixpanelLib.prototype.opt_out_tracking;
|
|
|
|
|
MixpanelLib.prototype['opt_in_tracking'] = MixpanelLib.prototype.opt_in_tracking;
|
|
|
|
|
MixpanelLib.prototype['has_opted_out_tracking'] = MixpanelLib.prototype.has_opted_out_tracking;
|
|
|
|
|
MixpanelLib.prototype['has_opted_in_tracking'] = MixpanelLib.prototype.has_opted_in_tracking;
|
|
|
|
|
MixpanelLib.prototype['clear_opt_in_out_tracking'] = MixpanelLib.prototype.clear_opt_in_out_tracking;
|
|
|
|
|
|
|
|
|
|
// MixpanelPersistence Exports
|
|
|
|
|
MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;
|
|
|
|
|
MixpanelPersistence.prototype['update_search_keyword'] = MixpanelPersistence.prototype.update_search_keyword;
|
|
|
|
|
MixpanelPersistence.prototype['update_referrer_info'] = MixpanelPersistence.prototype.update_referrer_info;
|
|
|
|
|
MixpanelPersistence.prototype['get_cross_subdomain'] = MixpanelPersistence.prototype.get_cross_subdomain;
|
|
|
|
|
MixpanelPersistence.prototype['clear'] = MixpanelPersistence.prototype.clear;
|
|
|
|
|
|
|
|
|
|
// MixpanelPeople Exports
|
|
|
|
|
MixpanelPeople.prototype['set'] = MixpanelPeople.prototype.set;
|
|
|
|
|
MixpanelPeople.prototype['set_once'] = MixpanelPeople.prototype.set_once;
|
|
|
|
|
MixpanelPeople.prototype['unset'] = MixpanelPeople.prototype.unset;
|
|
|
|
|
MixpanelPeople.prototype['increment'] = MixpanelPeople.prototype.increment;
|
|
|
|
|
MixpanelPeople.prototype['append'] = MixpanelPeople.prototype.append;
|
|
|
|
|
MixpanelPeople.prototype['union'] = MixpanelPeople.prototype.union;
|
|
|
|
|
MixpanelPeople.prototype['track_charge'] = MixpanelPeople.prototype.track_charge;
|
|
|
|
|
MixpanelPeople.prototype['clear_charges'] = MixpanelPeople.prototype.clear_charges;
|
|
|
|
|
MixpanelPeople.prototype['delete_user'] = MixpanelPeople.prototype.delete_user;
|
|
|
|
|
MixpanelPeople.prototype['toString'] = MixpanelPeople.prototype.toString;
|
|
|
|
|
|
|
|
|
|
_.safewrap_class(MixpanelLib, ['identify', '_check_and_handle_notifications', '_show_notification']);
|
|
|
|
|
|
|
|
|
|
var instances = {};
|
|
|
|
|
var extend_mp = function() {
|
|
|
|
|
// add all the sub mixpanel instances
|
|
|
|
|
_.each(instances, function(instance, name) {
|
|
|
|
|
if (name !== PRIMARY_INSTANCE_NAME) { mixpanel_master[name] = instance; }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// add private functions as _
|
|
|
|
|
mixpanel_master['_'] = _;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var override_mp_init_func = function() {
|
|
|
|
|
// we override the snippets init function to handle the case where a
|
|
|
|
|
// user initializes the mixpanel library after the script loads & runs
|
|
|
|
|
mixpanel_master['init'] = function(token, config, name) {
|
|
|
|
|
if (name) {
|
|
|
|
|
// initialize a sub library
|
|
|
|
|
if (!mixpanel_master[name]) {
|
|
|
|
|
mixpanel_master[name] = instances[name] = create_mplib(token, config, name);
|
|
|
|
|
mixpanel_master[name]._loaded();
|
|
|
|
|
}
|
|
|
|
|
return mixpanel_master[name];
|
|
|
|
|
} else {
|
|
|
|
|
var instance = mixpanel_master;
|
|
|
|
|
|
|
|
|
|
if (instances[PRIMARY_INSTANCE_NAME]) {
|
|
|
|
|
// main mixpanel lib already initialized
|
|
|
|
|
instance = instances[PRIMARY_INSTANCE_NAME];
|
|
|
|
|
} else if (token) {
|
|
|
|
|
// intialize the main mixpanel lib
|
|
|
|
|
instance = create_mplib(token, config, PRIMARY_INSTANCE_NAME);
|
|
|
|
|
instance._loaded();
|
|
|
|
|
instances[PRIMARY_INSTANCE_NAME] = instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mixpanel_master = instance;
|
|
|
|
|
if (init_type === INIT_SNIPPET) {
|
|
|
|
|
window$1[PRIMARY_INSTANCE_NAME] = mixpanel_master;
|
|
|
|
|
}
|
|
|
|
|
extend_mp();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var add_dom_loaded_handler = function() {
|
|
|
|
|
// Cross browser DOM Loaded support
|
|
|
|
|
function dom_loaded_handler() {
|
|
|
|
|
// function flag since we only want to execute this once
|
|
|
|
|
if (dom_loaded_handler.done) { return; }
|
|
|
|
|
dom_loaded_handler.done = true;
|
|
|
|
|
|
|
|
|
|
DOM_LOADED = true;
|
|
|
|
|
ENQUEUE_REQUESTS = false;
|
|
|
|
|
|
|
|
|
|
_.each(instances, function(inst) {
|
|
|
|
|
inst._dom_loaded();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function do_scroll_check() {
|
|
|
|
|
try {
|
|
|
|
|
document$1.documentElement.doScroll('left');
|
|
|
|
|
} catch(e) {
|
|
|
|
|
setTimeout(do_scroll_check, 1);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dom_loaded_handler();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (document$1.addEventListener) {
|
|
|
|
|
if (document$1.readyState === 'complete') {
|
|
|
|
|
// safari 4 can fire the DOMContentLoaded event before loading all
|
|
|
|
|
// external JS (including this file). you will see some copypasta
|
|
|
|
|
// on the internet that checks for 'complete' and 'loaded', but
|
|
|
|
|
// 'loaded' is an IE thing
|
|
|
|
|
dom_loaded_handler();
|
|
|
|
|
} else {
|
|
|
|
|
document$1.addEventListener('DOMContentLoaded', dom_loaded_handler, false);
|
|
|
|
|
}
|
|
|
|
|
} else if (document$1.attachEvent) {
|
|
|
|
|
// IE
|
|
|
|
|
document$1.attachEvent('onreadystatechange', dom_loaded_handler);
|
|
|
|
|
|
|
|
|
|
// check to make sure we arn't in a frame
|
|
|
|
|
var toplevel = false;
|
|
|
|
|
try {
|
|
|
|
|
toplevel = window$1.frameElement === null;
|
|
|
|
|
} catch(e) {
|
|
|
|
|
// noop
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (document$1.documentElement.doScroll && toplevel) {
|
|
|
|
|
do_scroll_check();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// fallback handler, always will work
|
|
|
|
|
_.register_event(window$1, 'load', dom_loaded_handler, true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function init_as_module() {
|
|
|
|
|
init_type = INIT_MODULE;
|
|
|
|
|
mixpanel_master = new MixpanelLib();
|
|
|
|
|
|
|
|
|
|
override_mp_init_func();
|
|
|
|
|
mixpanel_master['init']();
|
|
|
|
|
add_dom_loaded_handler();
|
|
|
|
|
|
|
|
|
|
return mixpanel_master;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var mixpanel = init_as_module();
|
|
|
|
|
|
|
|
|
|
module.exports = mixpanel;
|
|
|
|
|
},{}],2:[function(require,module,exports){
|
|
|
|
|
"use strict";
|
|
|
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
|
|
|
};
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
2018-07-13 16:15:04 +09:00
|
|
|
var click_action_1 = __importDefault(require("./actions/click-action"));
|
|
|
|
|
var ActionFactory = /** @class */ (function () {
|
|
|
|
|
function ActionFactory() {
|
|
|
|
|
}
|
|
|
|
|
ActionFactory.createAction = function (type, config) {
|
|
|
|
|
// Check if the action is available
|
|
|
|
|
if (ActionFactory.classDictionary[type] == undefined) {
|
|
|
|
|
throw new Error('[BitAnalytics] ' + type + ' is not available.');
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Create a action
|
|
|
|
|
var action = Object.create(ActionFactory.classDictionary[type].prototype);
|
|
|
|
|
action.constructor.apply(action, [config]);
|
|
|
|
|
action = action;
|
|
|
|
|
return action;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
ActionFactory.classDictionary = {
|
|
|
|
|
'click': click_action_1.default
|
|
|
|
|
};
|
|
|
|
|
return ActionFactory;
|
|
|
|
|
}());
|
|
|
|
|
exports.default = ActionFactory;
|
|
|
|
|
|
|
|
|
|
},{"./actions/click-action":5}],3:[function(require,module,exports){
|
|
|
|
|
"use strict";
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
|
|
var ActionHandlers = /** @class */ (function () {
|
|
|
|
|
function ActionHandlers() {
|
|
|
|
|
this.actions = [];
|
|
|
|
|
var self = this;
|
|
|
|
|
var callback = function () {
|
|
|
|
|
if (self.timeout) {
|
|
|
|
|
clearTimeout(self.timeout);
|
|
|
|
|
}
|
|
|
|
|
self.timeout = setTimeout(function () {
|
|
|
|
|
console.log('[BitAnalytics] Content modified, refreshing trackers');
|
|
|
|
|
self.refreshTrackers();
|
|
|
|
|
self.timeout = undefined;
|
|
|
|
|
}, 300);
|
|
|
|
|
};
|
|
|
|
|
if (MutationObserver) {
|
|
|
|
|
var targetNode = document.getElementsByTagName('body');
|
|
|
|
|
var config = { attributes: true, childList: true, subtree: true };
|
|
|
|
|
// Create an observer instance linked to the callback function
|
|
|
|
|
var observer = new MutationObserver(callback);
|
|
|
|
|
// Start observing the target node for configured mutations
|
|
|
|
|
observer.observe(targetNode[0], config);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
window.addEventListener("DOMSubtreeModified", callback);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Public methods
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
ActionHandlers.prototype.refreshTrackers = function () {
|
|
|
|
|
this.actions.map(function (action) {
|
|
|
|
|
try {
|
|
|
|
|
action.stopTracking();
|
|
|
|
|
}
|
|
|
|
|
catch (err) {
|
|
|
|
|
console.log(err);
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
action.startTracking();
|
|
|
|
|
}
|
|
|
|
|
catch (err) {
|
|
|
|
|
console.log(err);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
ActionHandlers.prototype.trackAction = function (action) {
|
|
|
|
|
this.actions.push(action);
|
|
|
|
|
};
|
|
|
|
|
return ActionHandlers;
|
|
|
|
|
}());
|
|
|
|
|
exports.default = ActionHandlers;
|
|
|
|
|
|
|
|
|
|
},{}],4:[function(require,module,exports){
|
|
|
|
|
"use strict";
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
|
|
var Action = /** @class */ (function () {
|
|
|
|
|
function Action(config) {
|
|
|
|
|
if (!config.name) {
|
|
|
|
|
throw new Error('[BitAnalytics] Action should have a name config : { name : ... }');
|
|
|
|
|
}
|
|
|
|
|
this.name = config.name;
|
|
|
|
|
this.isTracking = false;
|
|
|
|
|
}
|
|
|
|
|
return Action;
|
|
|
|
|
}());
|
|
|
|
|
exports.default = Action;
|
|
|
|
|
|
|
|
|
|
},{}],5:[function(require,module,exports){
|
|
|
|
|
"use strict";
|
|
|
|
|
var __extends = (this && this.__extends) || (function () {
|
|
|
|
|
var extendStatics = Object.setPrototypeOf ||
|
|
|
|
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
|
|
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
|
|
|
|
return function (d, b) {
|
|
|
|
|
extendStatics(d, b);
|
|
|
|
|
function __() { this.constructor = d; }
|
|
|
|
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
|
|
|
};
|
|
|
|
|
})();
|
|
|
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
|
|
|
};
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
|
|
var action_1 = __importDefault(require("../action"));
|
|
|
|
|
var log_event_handlers_1 = __importDefault(require("../log-event-handlers"));
|
|
|
|
|
var log_event_1 = __importDefault(require("../log-event"));
|
|
|
|
|
var ClickAction = /** @class */ (function (_super) {
|
|
|
|
|
__extends(ClickAction, _super);
|
|
|
|
|
function ClickAction(config) {
|
|
|
|
|
var _this = _super.call(this, config) || this;
|
|
|
|
|
_this.params = [];
|
|
|
|
|
if (!config.class || !config.channels) {
|
|
|
|
|
throw new Error('[BitAnalytics] ClickAction should have a config like this : { class : ..., channels: ... }');
|
|
|
|
|
}
|
|
|
|
|
if (config.params) {
|
|
|
|
|
_this.params = config.params;
|
|
|
|
|
}
|
|
|
|
|
_this.class = config.class;
|
|
|
|
|
_this.channels = config.channels;
|
|
|
|
|
var self = _this;
|
|
|
|
|
_this.listener = function (event) {
|
|
|
|
|
/*console.log('on click');
|
|
|
|
|
console.log(event.target.id);
|
|
|
|
|
console.log(event.target.outerHTML);
|
|
|
|
|
console.log(event.target.outerText);*/
|
|
|
|
|
var params = {};
|
|
|
|
|
var target = _this.searchTarget(event.srcElement);
|
|
|
|
|
// If I found my element, that should happen 100%
|
|
|
|
|
if (target) {
|
|
|
|
|
self.params.map(function (param) {
|
|
|
|
|
var value = target[param];
|
|
|
|
|
if (value) {
|
|
|
|
|
params[param] = value;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
var item = target.attributes.getNamedItem(param);
|
|
|
|
|
if (item) {
|
|
|
|
|
params[param] = item.value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
var logEvent = new log_event_1.default(self.name, [params], self.channels);
|
|
|
|
|
log_event_handlers_1.default.sharedInstance().postEvent(logEvent);
|
|
|
|
|
};
|
|
|
|
|
_this.isTracking = false;
|
|
|
|
|
return _this;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Private methods
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
ClickAction.prototype.searchTarget = function (element) {
|
|
|
|
|
if (element && element.classList && element.classList.contains(this.class)) {
|
|
|
|
|
return element;
|
|
|
|
|
}
|
|
|
|
|
else if (element.parentElement) {
|
|
|
|
|
return this.searchTarget(element.parentElement);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Public methods
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
ClickAction.prototype.startTracking = function () {
|
|
|
|
|
if (this.isTracking) {
|
|
|
|
|
throw new Error('[BitAnalytics] The tacking is already started');
|
|
|
|
|
}
|
|
|
|
|
this.isTracking = true;
|
|
|
|
|
var elements = document.getElementsByClassName(this.class);
|
|
|
|
|
// Add event listener to all the elements found
|
|
|
|
|
for (var i = 0; i < elements.length; i++) {
|
|
|
|
|
var element = elements[i];
|
|
|
|
|
console.log('init ' + this.name);
|
|
|
|
|
element.addEventListener('click', this.listener);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
ClickAction.prototype.stopTracking = function () {
|
|
|
|
|
if (!this.isTracking) {
|
|
|
|
|
throw new Error('[BitAnalytics] The tacking is already stopped');
|
|
|
|
|
}
|
|
|
|
|
var elements = document.getElementsByClassName(this.class);
|
|
|
|
|
// Add event listener to all the elements found
|
|
|
|
|
for (var i = 0; i < elements.length; i++) {
|
|
|
|
|
var element = elements[i];
|
|
|
|
|
element.removeEventListener('click', this.listener);
|
|
|
|
|
}
|
|
|
|
|
this.isTracking = false;
|
|
|
|
|
};
|
|
|
|
|
return ClickAction;
|
|
|
|
|
}(action_1.default));
|
|
|
|
|
exports.default = ClickAction;
|
|
|
|
|
|
2018-07-25 16:17:42 +09:00
|
|
|
},{"../action":4,"../log-event":16,"../log-event-handlers":15}],6:[function(require,module,exports){
|
2018-07-13 16:15:04 +09:00
|
|
|
"use strict";
|
|
|
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
|
|
|
};
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
2018-07-10 16:39:30 +09:00
|
|
|
var log_event_handlers_1 = __importDefault(require("./log-event-handlers"));
|
|
|
|
|
var log_event_1 = __importDefault(require("./log-event"));
|
2018-07-13 16:15:04 +09:00
|
|
|
var action_factory_1 = __importDefault(require("./action-factory"));
|
2018-07-10 16:39:30 +09:00
|
|
|
var adjust_channel_1 = __importDefault(require("./channels/adjust-channel"));
|
|
|
|
|
var mixpanel_channel_1 = __importDefault(require("./channels/mixpanel-channel"));
|
2018-07-13 16:15:04 +09:00
|
|
|
var action_handlers_1 = __importDefault(require("./action-handlers"));
|
2018-07-10 16:39:30 +09:00
|
|
|
var channels;
|
|
|
|
|
(function (channels) {
|
|
|
|
|
channels.AdjustChannel = adjust_channel_1.default;
|
|
|
|
|
channels.MixpanelChannel = mixpanel_channel_1.default;
|
|
|
|
|
})(channels = exports.channels || (exports.channels = {}));
|
|
|
|
|
var BitAnalytics = /** @class */ (function () {
|
|
|
|
|
function BitAnalytics() {
|
|
|
|
|
}
|
|
|
|
|
BitAnalytics.initialize = function (os, appVersion, channelConfigs) {
|
2018-07-13 16:15:04 +09:00
|
|
|
if (window == undefined) {
|
|
|
|
|
console.error('[BitAnalytics] BitAnalytics cannot be integrated in window.');
|
2018-07-10 16:39:30 +09:00
|
|
|
}
|
|
|
|
|
BitAnalytics.LogEventHandlers = new log_event_handlers_1.default(os, appVersion, channelConfigs);
|
2018-07-13 16:15:04 +09:00
|
|
|
BitAnalytics.ActionHandlers = new action_handlers_1.default();
|
2018-07-10 16:39:30 +09:00
|
|
|
BitAnalytics.LogEvent = log_event_1.default;
|
2018-07-13 16:15:04 +09:00
|
|
|
BitAnalytics.ActionFactory = action_factory_1.default;
|
2018-07-10 16:39:30 +09:00
|
|
|
};
|
|
|
|
|
BitAnalytics.main = function () {
|
|
|
|
|
if (window) {
|
|
|
|
|
window.BitAnalytics = BitAnalytics;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
return BitAnalytics;
|
|
|
|
|
}());
|
|
|
|
|
exports.default = BitAnalytics;
|
|
|
|
|
BitAnalytics.main();
|
|
|
|
|
|
2018-07-25 16:17:42 +09:00
|
|
|
},{"./action-factory":2,"./action-handlers":3,"./channels/adjust-channel":9,"./channels/mixpanel-channel":12,"./log-event":16,"./log-event-handlers":15}],7:[function(require,module,exports){
|
2018-07-10 16:39:30 +09:00
|
|
|
"use strict";
|
|
|
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
|
|
|
};
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
|
|
var adjust_channel_1 = __importDefault(require("./channels/adjust-channel"));
|
2018-07-13 16:15:04 +09:00
|
|
|
var firebase_channel_1 = __importDefault(require("./channels/firebase-channel"));
|
|
|
|
|
var ga_channel_1 = __importDefault(require("./channels/ga-channel"));
|
|
|
|
|
var mixpanel_channel_1 = __importDefault(require("./channels/mixpanel-channel"));
|
2018-07-10 16:39:30 +09:00
|
|
|
var ChannelFactory = /** @class */ (function () {
|
|
|
|
|
function ChannelFactory() {
|
|
|
|
|
}
|
|
|
|
|
ChannelFactory.createChannel = function (name, config) {
|
|
|
|
|
// Check if the channel is available
|
|
|
|
|
if (ChannelFactory.classDictionary[name] == undefined) {
|
2018-07-13 16:15:04 +09:00
|
|
|
throw new Error('[BitAnalytics] ' + name + ' is not available.');
|
2018-07-10 16:39:30 +09:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Create a channel
|
|
|
|
|
var channel = Object.create(ChannelFactory.classDictionary[name].prototype);
|
|
|
|
|
channel.constructor.apply(channel, [name, config]);
|
|
|
|
|
channel = channel;
|
|
|
|
|
return channel;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
ChannelFactory.classDictionary = {
|
2018-07-13 16:15:04 +09:00
|
|
|
'adjust': adjust_channel_1.default,
|
2018-07-10 16:39:30 +09:00
|
|
|
'firebase': firebase_channel_1.default,
|
2018-07-13 16:15:04 +09:00
|
|
|
'ga': ga_channel_1.default,
|
|
|
|
|
'mixpanel': mixpanel_channel_1.default
|
2018-07-10 16:39:30 +09:00
|
|
|
};
|
|
|
|
|
return ChannelFactory;
|
|
|
|
|
}());
|
|
|
|
|
exports.default = ChannelFactory;
|
|
|
|
|
|
2018-07-13 16:15:04 +09:00
|
|
|
},{"./channels/adjust-channel":9,"./channels/firebase-channel":10,"./channels/ga-channel":11,"./channels/mixpanel-channel":12}],8:[function(require,module,exports){
|
2018-07-10 16:39:30 +09:00
|
|
|
"use strict";
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
|
|
var Channel = /** @class */ (function () {
|
|
|
|
|
function Channel(name) {
|
|
|
|
|
this.isReady = false;
|
|
|
|
|
this.queue = new Array();
|
|
|
|
|
this.name = name;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Protected methods
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
Channel.prototype.flush = function () {
|
|
|
|
|
this.queue.forEach(function (f) {
|
|
|
|
|
f();
|
|
|
|
|
});
|
|
|
|
|
this.queue = new Array();
|
|
|
|
|
};
|
|
|
|
|
Channel.prototype.enqueue = function (f) {
|
|
|
|
|
this.queue.push(f);
|
|
|
|
|
};
|
|
|
|
|
return Channel;
|
|
|
|
|
}());
|
|
|
|
|
exports.default = Channel;
|
|
|
|
|
|
2018-07-13 16:15:04 +09:00
|
|
|
},{}],9:[function(require,module,exports){
|
2018-07-10 16:39:30 +09:00
|
|
|
"use strict";
|
|
|
|
|
var __extends = (this && this.__extends) || (function () {
|
|
|
|
|
var extendStatics = Object.setPrototypeOf ||
|
|
|
|
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
|
|
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
|
|
|
|
return function (d, b) {
|
|
|
|
|
extendStatics(d, b);
|
|
|
|
|
function __() { this.constructor = d; }
|
|
|
|
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
|
|
|
};
|
|
|
|
|
})();
|
|
|
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
|
|
|
};
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
|
|
var channel_1 = __importDefault(require("../channel"));
|
2018-07-13 16:15:04 +09:00
|
|
|
// Loading Adjust websdk
|
|
|
|
|
require('../external-libs/adjust');
|
2018-07-10 16:39:30 +09:00
|
|
|
var AdjustChannel = /** @class */ (function (_super) {
|
|
|
|
|
__extends(AdjustChannel, _super);
|
|
|
|
|
function AdjustChannel(name, config) {
|
|
|
|
|
var _this = _super.call(this, name) || this;
|
|
|
|
|
if (!config.token) {
|
2018-07-13 16:15:04 +09:00
|
|
|
throw new Error('[BitAnalytics] Adjust config is missing token.');
|
|
|
|
|
}
|
|
|
|
|
if (!config.eventTypes) {
|
|
|
|
|
throw new Error('[BitAnalytics] Adjust config is missing event types.');
|
|
|
|
|
}
|
|
|
|
|
if (!Adjust) {
|
|
|
|
|
throw new Error('[BitAnalytics] Adjust cordova plugin is not installed correctly.');
|
|
|
|
|
}
|
|
|
|
|
_this.eventTypes = config.eventTypes;
|
|
|
|
|
var os = _this.adjustedOs(config.os);
|
|
|
|
|
_this.advertisingId = _this.getAdvertisingId(os);
|
|
|
|
|
console.log('Advertising ID for adjust: ' + _this.advertisingId);
|
|
|
|
|
// TODO: Different initialisation for Cordova.
|
|
|
|
|
var sessionParams = {
|
|
|
|
|
app_version: config.appVersion,
|
|
|
|
|
app_version_short: config.appVersion,
|
|
|
|
|
os_name: os
|
|
|
|
|
};
|
|
|
|
|
_this.addAdvertisingId(os, sessionParams);
|
|
|
|
|
var environment = config.environment || 'production';
|
|
|
|
|
_this.adjustInstance = new Adjust(config.token, environment, os);
|
|
|
|
|
_this.adjustInstance.trackSession(sessionParams);
|
|
|
|
|
_this.isReady = true;
|
2018-07-10 16:39:30 +09:00
|
|
|
return _this;
|
|
|
|
|
}
|
2018-07-13 16:15:04 +09:00
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Public methods
|
|
|
|
|
*
|
|
|
|
|
*/
|
2018-07-10 16:39:30 +09:00
|
|
|
AdjustChannel.prototype.postEvent = function (name, params) {
|
|
|
|
|
if (this.isReady) {
|
2018-07-13 16:15:04 +09:00
|
|
|
var eventType = this.eventTypes[name];
|
|
|
|
|
// Each event needs to be added on adjust, and config for adjust.
|
|
|
|
|
if (!eventType) {
|
|
|
|
|
throw new Error('This event name does not exist on Adjust.');
|
|
|
|
|
}
|
|
|
|
|
params.os = this.adjustedOs(params.os);
|
|
|
|
|
this.addAdvertisingId(params.os, params);
|
|
|
|
|
this.adjustInstance.trackEvent(eventType, params);
|
2018-07-10 16:39:30 +09:00
|
|
|
}
|
|
|
|
|
};
|
2018-07-13 16:15:04 +09:00
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Private methods
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
AdjustChannel.prototype.addAdvertisingId = function (os, params) {
|
|
|
|
|
if (os === 'ios') {
|
|
|
|
|
params.idfa = this.advertisingId;
|
|
|
|
|
}
|
|
|
|
|
else if (os === 'android') {
|
|
|
|
|
params.gps_adid = this.advertisingId;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
params.win_hwid = this.advertisingId;
|
|
|
|
|
params.win_naid = this.advertisingId;
|
|
|
|
|
params.win_adid = this.advertisingId;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// Desktop version will pretend to be Windows
|
|
|
|
|
AdjustChannel.prototype.adjustedOs = function (os) {
|
|
|
|
|
if (os === 'ios' || os === 'android') {
|
|
|
|
|
return os;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
return 'wstore';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
AdjustChannel.prototype.generateRandomGuid = function () {
|
|
|
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
|
|
|
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
|
|
|
|
return v.toString(16);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
// Example: 107e8ea14329d4a2194ebbb6dc0c0fd7
|
|
|
|
|
AdjustChannel.prototype.generateWindowsAdvertisingId = function () {
|
|
|
|
|
var id = '';
|
|
|
|
|
for (var i = 0; i < 32; i++) {
|
|
|
|
|
id += Math.floor(Math.random() * 16).toString(16);
|
|
|
|
|
}
|
|
|
|
|
return id;
|
|
|
|
|
};
|
|
|
|
|
// https://docs.adjust.com/en/event-tracking/
|
|
|
|
|
AdjustChannel.prototype.getAdvertisingId = function (os) {
|
|
|
|
|
var adid = localStorage.getItem('adid');
|
|
|
|
|
if (!adid) {
|
|
|
|
|
if (os === 'ios') {
|
|
|
|
|
adid = this.generateRandomGuid().toUpperCase();
|
|
|
|
|
}
|
|
|
|
|
else if (os === 'android') {
|
|
|
|
|
adid = this.generateRandomGuid();
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
adid = this.generateWindowsAdvertisingId();
|
|
|
|
|
}
|
|
|
|
|
localStorage.setItem('adid', adid);
|
|
|
|
|
}
|
|
|
|
|
return adid;
|
|
|
|
|
};
|
2018-07-10 16:39:30 +09:00
|
|
|
return AdjustChannel;
|
|
|
|
|
}(channel_1.default));
|
|
|
|
|
exports.default = AdjustChannel;
|
|
|
|
|
|
2018-07-13 16:15:04 +09:00
|
|
|
},{"../channel":8,"../external-libs/adjust":13}],10:[function(require,module,exports){
|
2018-07-10 16:39:30 +09:00
|
|
|
"use strict";
|
|
|
|
|
var __extends = (this && this.__extends) || (function () {
|
|
|
|
|
var extendStatics = Object.setPrototypeOf ||
|
|
|
|
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
|
|
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
|
|
|
|
return function (d, b) {
|
|
|
|
|
extendStatics(d, b);
|
|
|
|
|
function __() { this.constructor = d; }
|
|
|
|
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
|
|
|
};
|
|
|
|
|
})();
|
|
|
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
|
|
|
};
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
|
|
var channel_1 = __importDefault(require("../channel"));
|
|
|
|
|
var FirebaseChannel = /** @class */ (function (_super) {
|
|
|
|
|
__extends(FirebaseChannel, _super);
|
|
|
|
|
function FirebaseChannel(name, config) {
|
|
|
|
|
var _this = _super.call(this, name) || this;
|
|
|
|
|
/**
|
|
|
|
|
* Firebase available only on ios and android
|
|
|
|
|
*/
|
2018-07-13 16:15:04 +09:00
|
|
|
if (config.os != 'android' && config.os != 'ios') {
|
|
|
|
|
throw new Error('[BitAnalytics] Firebase is not supported on ' + config.os);
|
2018-07-10 16:39:30 +09:00
|
|
|
}
|
|
|
|
|
if (!window.FirebasePlugin) {
|
2018-07-13 16:15:04 +09:00
|
|
|
throw new Error('[BitAnalytics] Firebase cordova plugin is not installed correctly.');
|
2018-07-10 16:39:30 +09:00
|
|
|
}
|
|
|
|
|
_this.firebaseInstance = window.FirebasePlugin;
|
|
|
|
|
_this.isReady = true;
|
|
|
|
|
return _this;
|
|
|
|
|
}
|
2018-07-13 16:15:04 +09:00
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Public methods
|
|
|
|
|
*
|
|
|
|
|
*/
|
2018-07-10 16:39:30 +09:00
|
|
|
FirebaseChannel.prototype.postEvent = function (name, params) {
|
|
|
|
|
var _this = this;
|
2018-07-16 12:40:17 +12:00
|
|
|
var sanitizedParams = this.sanitizeParams(params);
|
2018-07-10 16:39:30 +09:00
|
|
|
if (!this.isReady) {
|
2018-07-16 12:40:17 +12:00
|
|
|
this.enqueue(function () { _this.postEvent(name, sanitizedParams); });
|
2018-07-10 16:39:30 +09:00
|
|
|
}
|
|
|
|
|
else {
|
2018-07-16 12:40:17 +12:00
|
|
|
this.firebaseInstance.logEvent(name, sanitizedParams);
|
2018-07-10 16:39:30 +09:00
|
|
|
}
|
|
|
|
|
};
|
2018-07-16 12:40:17 +12:00
|
|
|
// [Firebase/Analytics][I-ACS013002] Event parameter name must contain only letters, numbers, or underscores
|
|
|
|
|
FirebaseChannel.prototype.sanitizeParams = function (params) {
|
|
|
|
|
var keys = Object.keys(params);
|
|
|
|
|
var keysLength = keys.length;
|
|
|
|
|
var sanitized = {};
|
2018-07-25 16:17:42 +09:00
|
|
|
keys.map(function (key) {
|
2018-07-16 12:40:17 +12:00
|
|
|
var cleanKey = key.replace('-', '_').replace(/[\W]+/g, '');
|
|
|
|
|
sanitized[cleanKey] = params[key];
|
2018-07-25 16:17:42 +09:00
|
|
|
});
|
2018-07-16 12:40:17 +12:00
|
|
|
return sanitized;
|
|
|
|
|
};
|
2018-07-10 16:39:30 +09:00
|
|
|
return FirebaseChannel;
|
|
|
|
|
}(channel_1.default));
|
|
|
|
|
exports.default = FirebaseChannel;
|
|
|
|
|
|
2018-07-13 16:15:04 +09:00
|
|
|
},{"../channel":8}],11:[function(require,module,exports){
|
|
|
|
|
"use strict";
|
|
|
|
|
var __extends = (this && this.__extends) || (function () {
|
|
|
|
|
var extendStatics = Object.setPrototypeOf ||
|
|
|
|
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
|
|
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
|
|
|
|
return function (d, b) {
|
|
|
|
|
extendStatics(d, b);
|
|
|
|
|
function __() { this.constructor = d; }
|
|
|
|
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
|
|
|
};
|
|
|
|
|
})();
|
|
|
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
|
|
|
};
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
|
|
var channel_1 = __importDefault(require("../channel"));
|
2018-07-25 16:17:42 +09:00
|
|
|
var ga_1 = __importDefault(require("../external-libs/ga"));
|
2018-07-13 16:15:04 +09:00
|
|
|
var GoogleAnalyticsChannel = /** @class */ (function (_super) {
|
|
|
|
|
__extends(GoogleAnalyticsChannel, _super);
|
|
|
|
|
function GoogleAnalyticsChannel(name, config) {
|
|
|
|
|
var _this = _super.call(this, name) || this;
|
|
|
|
|
_this.gaInstance = null;
|
|
|
|
|
_this.eventLabels = ['id'];
|
|
|
|
|
if (!config.trackingId) {
|
|
|
|
|
throw new Error('[BitAnalytics] Google Analytics config is missing tracking ID.');
|
|
|
|
|
}
|
|
|
|
|
if (config.eventLabels) {
|
|
|
|
|
_this.eventLabels = config.eventLabels;
|
|
|
|
|
}
|
2018-07-25 16:17:42 +09:00
|
|
|
_this.gaInstance = new ga_1.default({
|
|
|
|
|
trackID: config.trackingId,
|
|
|
|
|
appVersion: config.appVersion,
|
|
|
|
|
appName: config.appName || 'App'
|
|
|
|
|
});
|
|
|
|
|
_this.isReady = true;
|
2018-07-13 16:15:04 +09:00
|
|
|
return _this;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Public methods
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
GoogleAnalyticsChannel.prototype.postEvent = function (name, params) {
|
|
|
|
|
if (this.isReady) {
|
2018-07-25 16:17:42 +09:00
|
|
|
var category = name;
|
|
|
|
|
var action = name;
|
|
|
|
|
var label = name;
|
|
|
|
|
var value = params['value'] || '';
|
2018-07-13 16:15:04 +09:00
|
|
|
for (var _i = 0, _a = this.eventLabels; _i < _a.length; _i++) {
|
|
|
|
|
var eventLabel = _a[_i];
|
|
|
|
|
if (params[eventLabel]) {
|
2018-07-25 16:17:42 +09:00
|
|
|
label = params[eventLabel];
|
2018-07-13 16:15:04 +09:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-25 16:17:42 +09:00
|
|
|
this.gaInstance.event(category, action, label, value);
|
2018-07-13 16:15:04 +09:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
return GoogleAnalyticsChannel;
|
|
|
|
|
}(channel_1.default));
|
|
|
|
|
exports.default = GoogleAnalyticsChannel;
|
|
|
|
|
|
2018-07-25 16:17:42 +09:00
|
|
|
},{"../channel":8,"../external-libs/ga":14}],12:[function(require,module,exports){
|
2018-07-10 16:39:30 +09:00
|
|
|
"use strict";
|
|
|
|
|
var __extends = (this && this.__extends) || (function () {
|
|
|
|
|
var extendStatics = Object.setPrototypeOf ||
|
|
|
|
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
|
|
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
|
|
|
|
return function (d, b) {
|
|
|
|
|
extendStatics(d, b);
|
|
|
|
|
function __() { this.constructor = d; }
|
|
|
|
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
|
|
|
};
|
|
|
|
|
})();
|
|
|
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
|
|
|
};
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
|
|
var channel_1 = __importDefault(require("../channel"));
|
|
|
|
|
var mixpanel = require('mixpanel-browser');
|
|
|
|
|
var MixpanelChannel = /** @class */ (function (_super) {
|
|
|
|
|
__extends(MixpanelChannel, _super);
|
|
|
|
|
function MixpanelChannel(name, config) {
|
|
|
|
|
var _this = _super.call(this, name) || this;
|
|
|
|
|
if (!config.token) {
|
2018-07-13 16:15:04 +09:00
|
|
|
throw new DOMException('[BitAnalytics] Config incorrect.');
|
2018-07-10 16:39:30 +09:00
|
|
|
}
|
|
|
|
|
_this.mixpanelInstance = mixpanel;
|
|
|
|
|
mixpanel.init(config.token, config.config);
|
|
|
|
|
return _this;
|
|
|
|
|
}
|
2018-07-13 16:15:04 +09:00
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Public methods
|
|
|
|
|
*
|
|
|
|
|
*/
|
2018-07-10 16:39:30 +09:00
|
|
|
MixpanelChannel.prototype.postEvent = function (name, params) {
|
|
|
|
|
var result = this.mixpanelInstance.track(name);
|
|
|
|
|
};
|
|
|
|
|
return MixpanelChannel;
|
|
|
|
|
}(channel_1.default));
|
|
|
|
|
exports.default = MixpanelChannel;
|
|
|
|
|
|
2018-07-13 16:15:04 +09:00
|
|
|
},{"../channel":8,"mixpanel-browser":1}],13:[function(require,module,exports){
|
2018-07-10 16:39:30 +09:00
|
|
|
"use strict";
|
|
|
|
|
(function (window) {
|
|
|
|
|
var sendRequest = function (method, url, data, success_cb, error_cb) {
|
|
|
|
|
var req = new XMLHttpRequest();
|
|
|
|
|
req.open(method, url, !0);
|
|
|
|
|
req.setRequestHeader("Client-SDK", "js4.0.0");
|
|
|
|
|
req.onreadystatechange = function () { if (req.readyState == 4) {
|
|
|
|
|
if (req.status >= 200 && req.status < 400) {
|
|
|
|
|
!!success_cb && success_cb(req.responseText);
|
|
|
|
|
}
|
|
|
|
|
else if (!!error_cb) {
|
|
|
|
|
!!error_cb && error_cb(new Error("Server responded with HTTP " + req.status), xhr);
|
|
|
|
|
}
|
|
|
|
|
} };
|
|
|
|
|
if (!!error_cb) {
|
|
|
|
|
req.onerror = error_cb;
|
|
|
|
|
}
|
|
|
|
|
req.send(data);
|
|
|
|
|
};
|
|
|
|
|
var encodeQueryString = function (params) {
|
|
|
|
|
var pairs = [];
|
|
|
|
|
for (var k in params) {
|
|
|
|
|
if (!params.hasOwnProperty(k)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
pairs.push(encodeURIComponent(k) + "=" + encodeURIComponent(params[k]));
|
|
|
|
|
}
|
|
|
|
|
return pairs.join("&");
|
|
|
|
|
};
|
|
|
|
|
var cloneObj = function (obj) {
|
|
|
|
|
var copy = {};
|
|
|
|
|
if (typeof (obj) != "object" || !obj) {
|
|
|
|
|
return copy;
|
|
|
|
|
}
|
|
|
|
|
for (var k in obj) {
|
|
|
|
|
if (!obj.hasOwnProperty(k)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
copy[k] = obj[k];
|
|
|
|
|
}
|
|
|
|
|
return copy;
|
|
|
|
|
};
|
|
|
|
|
if (!'withCredentials' in new XMLHttpRequest()) {
|
|
|
|
|
sendRequest = function () { };
|
|
|
|
|
}
|
|
|
|
|
window.Adjust = function (app_token, environment, os_name) { this.trackSession = function (device_ids) { var params = cloneObj(device_ids); params.app_token = app_token; params.os_name = os_name; params.environment = environment; sendRequest("GET", "https://app.adjust.com/session?" + encodeQueryString(params)); }; this.trackEvent = function (event_token, device_ids) { var params = cloneObj(device_ids); params.app_token = app_token; params.event_token = event_token; params.os_name = os_name; params.environment = environment; sendRequest("GET", "https://app.adjust.com/event?" + encodeQueryString(params)); }; };
|
|
|
|
|
})(window);
|
|
|
|
|
|
2018-07-13 16:15:04 +09:00
|
|
|
},{}],14:[function(require,module,exports){
|
2018-07-10 16:39:30 +09:00
|
|
|
"use strict";
|
2018-07-25 16:17:42 +09:00
|
|
|
/*
|
|
|
|
|
* name: nwjs-analytics -Node-Webkit Google Analytics integration
|
|
|
|
|
* version: 1.0.2
|
|
|
|
|
* github: https://github.com/Daaru00/nwjs-analytics
|
|
|
|
|
*/
|
|
|
|
|
function GA(opt) {
|
|
|
|
|
this.apiVersion = opt.apiVersion || '1';
|
|
|
|
|
this.trackID = opt.trackID || 'UA-XXXXXXXX-X';
|
|
|
|
|
this.clientID = opt.clientID || null;
|
|
|
|
|
this.userID = opt.userID || null;
|
|
|
|
|
this.appName = opt.appName || 'App';
|
|
|
|
|
this.appVersion = opt.appVersion || '1.0.0';
|
|
|
|
|
this.debug = opt.debug || false;
|
|
|
|
|
this.performanceTracking = opt.performanceTracking || true;
|
|
|
|
|
this.errorTracking = opt.errorTracking || true;
|
|
|
|
|
this.userLanguage = opt.userLanguage || "en";
|
|
|
|
|
this.currency = opt.currency || "EUR";
|
|
|
|
|
this.lastScreenName = opt.lastScreenName || '';
|
|
|
|
|
}
|
|
|
|
|
GA.prototype.sendRequest = function (data, callback) {
|
|
|
|
|
var ga = this;
|
|
|
|
|
if (!this.clientID || this.clientID == null)
|
|
|
|
|
this.clientID = this.generateClientID();
|
|
|
|
|
if (!this.userID || this.userID == null)
|
|
|
|
|
this.userID = this.generateClientID();
|
|
|
|
|
var postData = "v=" + this.apiVersion
|
|
|
|
|
+ "&an=" + this.appName
|
|
|
|
|
+ "&av=" + this.appVersion
|
|
|
|
|
+ "&tid=" + this.trackID
|
|
|
|
|
+ "&cid=" + this.clientID
|
|
|
|
|
+ "&sr=" + this.getScreenResolution()
|
|
|
|
|
+ "&vp=" + this.getViewportSize();
|
|
|
|
|
Object.keys(data).forEach(function (key) {
|
|
|
|
|
var val = data[key];
|
|
|
|
|
if (typeof val != "undefined")
|
|
|
|
|
postData += "&" + key + "=" + val;
|
|
|
|
|
});
|
|
|
|
|
var http = new XMLHttpRequest();
|
|
|
|
|
var url = "https://www.google-analytics.com";
|
|
|
|
|
if (!this.debug)
|
|
|
|
|
url += "/collect";
|
|
|
|
|
else
|
|
|
|
|
url += "/debug/collect";
|
|
|
|
|
http.open("GET", url + "?" + postData, true);
|
|
|
|
|
http.onreadystatechange = function () {
|
|
|
|
|
if (ga.debug)
|
|
|
|
|
console.log(http.response);
|
|
|
|
|
if (http.readyState == 4 && http.status == 200) {
|
|
|
|
|
if (callback)
|
|
|
|
|
callback(true);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (callback)
|
|
|
|
|
callback(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
http.send();
|
|
|
|
|
};
|
|
|
|
|
GA.prototype.generateClientID = function () {
|
|
|
|
|
var id = "";
|
|
|
|
|
var possibilities = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
|
|
|
for (var i = 0; i < 5; i++)
|
|
|
|
|
id += possibilities.charAt(Math.floor(Math.random() * possibilities.length));
|
|
|
|
|
return id;
|
|
|
|
|
};
|
|
|
|
|
GA.prototype.getScreenResolution = function () {
|
|
|
|
|
return screen.width + "x" + screen.height;
|
|
|
|
|
};
|
|
|
|
|
GA.prototype.getColorDept = function () {
|
|
|
|
|
return screen.colorDepth + "-bits";
|
|
|
|
|
};
|
|
|
|
|
GA.prototype.getUserAgent = function () {
|
|
|
|
|
return navigator.userAgent;
|
|
|
|
|
};
|
|
|
|
|
GA.prototype.getViewportSize = function () {
|
|
|
|
|
return window.screen.availWidth + "x" + window.screen.availHeight;
|
|
|
|
|
};
|
|
|
|
|
/*
|
|
|
|
|
* Measurement Protocol
|
|
|
|
|
* [https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide]
|
|
|
|
|
*/
|
|
|
|
|
GA.prototype.screenView = function (screename) {
|
|
|
|
|
var data = {
|
|
|
|
|
't': 'screenview',
|
|
|
|
|
'cd': screename
|
|
|
|
|
};
|
|
|
|
|
this.sendRequest(data);
|
|
|
|
|
this.lastScreenName = screename;
|
|
|
|
|
};
|
|
|
|
|
GA.prototype.event = function (category, action, label, value) {
|
|
|
|
|
var data = {
|
|
|
|
|
't': 'event',
|
|
|
|
|
'ec': category,
|
|
|
|
|
'ea': action,
|
|
|
|
|
};
|
|
|
|
|
if (label) {
|
|
|
|
|
data['el'] = label;
|
|
|
|
|
}
|
|
|
|
|
if (value) {
|
|
|
|
|
data['ev'] = value;
|
|
|
|
|
}
|
|
|
|
|
if (this.lastScreenName) {
|
|
|
|
|
data['cd'] = this.lastScreenName;
|
|
|
|
|
}
|
|
|
|
|
this.sendRequest(data);
|
|
|
|
|
};
|
|
|
|
|
GA.prototype.exception = function (msg, fatal) {
|
|
|
|
|
var data = {
|
|
|
|
|
't': 'exception',
|
|
|
|
|
'exd': msg,
|
|
|
|
|
'exf': fatal || 0
|
|
|
|
|
};
|
|
|
|
|
this.sendRequest(data);
|
|
|
|
|
};
|
|
|
|
|
GA.prototype.timing = function (category, variable, time, label) {
|
|
|
|
|
var data = {
|
|
|
|
|
't': 'timing',
|
|
|
|
|
'utc': category,
|
|
|
|
|
'utv': variable,
|
|
|
|
|
'utt': time,
|
|
|
|
|
'utl': label,
|
|
|
|
|
};
|
|
|
|
|
this.sendRequest(data);
|
|
|
|
|
},
|
|
|
|
|
GA.prototype.ecommerce = {
|
|
|
|
|
transactionID: false,
|
|
|
|
|
generateTransactionID: function () {
|
|
|
|
|
var id = "";
|
|
|
|
|
var possibilities = "0123456789";
|
|
|
|
|
for (var i = 0; i < 5; i++)
|
|
|
|
|
id += possibilities.charAt(Math.floor(Math.random() * possibilities.length));
|
|
|
|
|
return id;
|
|
|
|
|
},
|
|
|
|
|
transaction: function (total, items) {
|
|
|
|
|
var t_id = "";
|
|
|
|
|
if (!this.ecommerce.transactionID)
|
|
|
|
|
t_id = this.ecommerce.generateTransactionID();
|
|
|
|
|
else
|
|
|
|
|
t_id = this.ecommerce.transactionID;
|
|
|
|
|
var data = {
|
|
|
|
|
't': 'transaction',
|
|
|
|
|
'ti': t_id,
|
|
|
|
|
'tr': total,
|
|
|
|
|
'cu': this.currency,
|
|
|
|
|
};
|
|
|
|
|
this.sendRequest(data);
|
|
|
|
|
items.forEach(function (item) {
|
|
|
|
|
var data = {
|
|
|
|
|
't': 'item',
|
|
|
|
|
'ti': t_id,
|
|
|
|
|
'in': item.name,
|
|
|
|
|
'ip': item.price,
|
|
|
|
|
'iq': item.qty,
|
|
|
|
|
'ic': item.id,
|
|
|
|
|
'cu': this.currency
|
|
|
|
|
};
|
|
|
|
|
this.sendRequest(data);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
GA.prototype.custom = function (data) {
|
|
|
|
|
this.sendRequest(data);
|
|
|
|
|
};
|
|
|
|
|
module.exports = GA;
|
|
|
|
|
/*
|
|
|
|
|
* Performance Tracking
|
|
|
|
|
*/
|
|
|
|
|
/*window.addEventListener("load", function() {
|
|
|
|
|
|
|
|
|
|
if(ga.performanceTracking) {
|
|
|
|
|
setTimeout(function() {
|
|
|
|
|
var timing = window.performance.timing;
|
|
|
|
|
var userTime = timing.loadEventEnd - timing.navigationStart;
|
|
|
|
|
ga.timing("performance", "pageload", userTime);
|
|
|
|
|
}, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}, false);*/
|
|
|
|
|
/*
|
|
|
|
|
* Error Reporting
|
|
|
|
|
*/
|
|
|
|
|
/*window.onerror = function (msg, url, lineNo, columnNo, error) {
|
|
|
|
|
var message = [
|
|
|
|
|
'Message: ' + msg,
|
|
|
|
|
'Line: ' + lineNo,
|
|
|
|
|
'Column: ' + columnNo,
|
|
|
|
|
'Error object: ' + JSON.stringify(error)
|
|
|
|
|
].join(' - ');
|
|
|
|
|
|
|
|
|
|
if(ga.errorTracking)
|
|
|
|
|
{
|
|
|
|
|
setTimeout(function() {
|
|
|
|
|
ga.exception(message.toString());
|
|
|
|
|
}, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
};*/
|
|
|
|
|
|
|
|
|
|
},{}],15:[function(require,module,exports){
|
|
|
|
|
"use strict";
|
2018-07-10 16:39:30 +09:00
|
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
|
|
|
};
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
|
|
var channel_factory_1 = __importDefault(require("./channel-factory"));
|
|
|
|
|
var LogEventHandlers = /** @class */ (function () {
|
|
|
|
|
function LogEventHandlers(os, appVersion, channelConfigs) {
|
|
|
|
|
this.os = os;
|
|
|
|
|
this.appVersion = appVersion;
|
|
|
|
|
this.channels = [];
|
|
|
|
|
this.isReady = false; // Ready once all channels are ready or one channel is ready?
|
2018-07-13 16:15:04 +09:00
|
|
|
this.initializeChannels(channelConfigs);
|
2018-07-10 16:39:30 +09:00
|
|
|
LogEventHandlers.instance = this;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Public methods
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
LogEventHandlers.sharedInstance = function () {
|
|
|
|
|
if (LogEventHandlers.instance) {
|
|
|
|
|
return LogEventHandlers.instance;
|
|
|
|
|
}
|
|
|
|
|
else {
|
2018-07-13 16:15:04 +09:00
|
|
|
throw new Error('[BitAnalytics] LogEventHandlers need to be initialized');
|
2018-07-10 16:39:30 +09:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
LogEventHandlers.prototype.postEvent = function (logEvent) {
|
|
|
|
|
var _this = this;
|
|
|
|
|
/**
|
|
|
|
|
* 0 is shared params
|
|
|
|
|
* 1 is first channel
|
|
|
|
|
* 2 is second channel
|
|
|
|
|
* ...
|
|
|
|
|
*/
|
2018-07-13 16:15:04 +09:00
|
|
|
var logEventParams = logEvent.params;
|
2018-07-10 16:39:30 +09:00
|
|
|
// params
|
|
|
|
|
var params = {
|
|
|
|
|
'os': this.os,
|
|
|
|
|
'appVersion': this.appVersion
|
|
|
|
|
};
|
|
|
|
|
// Concat the shared params
|
|
|
|
|
if (logEventParams.length > 0) {
|
|
|
|
|
// concat specific params needed
|
2018-07-13 16:15:04 +09:00
|
|
|
params = this.concatObject(logEventParams[0], params);
|
2018-07-10 16:39:30 +09:00
|
|
|
}
|
|
|
|
|
// Post event depending of the channel
|
2018-07-13 16:15:04 +09:00
|
|
|
logEvent.channelNames.map(function (channelName, i) {
|
2018-07-10 16:39:30 +09:00
|
|
|
var channel = _this.getChannelByName(channelName);
|
|
|
|
|
if (channel) {
|
|
|
|
|
// Real index (first param is shared by all channels)
|
|
|
|
|
var index = i + 1;
|
|
|
|
|
// concat if needed
|
|
|
|
|
if (logEventParams.length > index) {
|
|
|
|
|
// concat specific params needed
|
2018-07-13 16:15:04 +09:00
|
|
|
params = _this.concatObject(logEventParams[index], params);
|
|
|
|
|
}
|
|
|
|
|
console.log('[BitAnalytics] Params: ' + JSON.stringify(params));
|
|
|
|
|
try {
|
|
|
|
|
channel.postEvent(logEvent.name, params);
|
|
|
|
|
console.log('[BitAnalytics] LogEvent "' + logEvent.name + '" sent to ' + channelName + '.');
|
|
|
|
|
}
|
|
|
|
|
catch (e) {
|
|
|
|
|
console.error('[BitAnalytics] LogEvent "' + logEvent.name + '" failed to send with "' + channelName + '. ');
|
|
|
|
|
console.log(e);
|
2018-07-10 16:39:30 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Channel not available
|
2018-07-13 16:15:04 +09:00
|
|
|
console.log('[BitAnalytics] LogEvent "' + logEvent.name + '" cannot send to ' + channelName + ', this channel is not available.');
|
2018-07-10 16:39:30 +09:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Private methods
|
|
|
|
|
*
|
|
|
|
|
*/
|
2018-07-13 16:15:04 +09:00
|
|
|
LogEventHandlers.prototype.concatObject = function (from, to) {
|
|
|
|
|
var keys = Object.keys(from);
|
|
|
|
|
keys.map(function (key) {
|
|
|
|
|
if (!to[key]) {
|
|
|
|
|
to[key] = from[key];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return to;
|
|
|
|
|
};
|
2018-07-10 16:39:30 +09:00
|
|
|
LogEventHandlers.prototype.getChannelByName = function (channelName) {
|
2018-07-13 16:15:04 +09:00
|
|
|
var channels = this.channels.filter(function (channel) { return channel.name == channelName; });
|
2018-07-10 16:39:30 +09:00
|
|
|
if (channels.length > 0) {
|
|
|
|
|
return channels[0];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
2018-07-13 16:15:04 +09:00
|
|
|
LogEventHandlers.prototype.initializeChannels = function (channelConfigs) {
|
|
|
|
|
var _this = this;
|
|
|
|
|
// Get the channel names by the keys
|
|
|
|
|
var channelNames = Object.keys(channelConfigs);
|
|
|
|
|
// Iterate to init the several channels given in the config
|
|
|
|
|
channelNames.map(function (channelName) {
|
|
|
|
|
var channelConfig = channelConfigs[channelName];
|
|
|
|
|
// OS shared to check the availability of this channel on this OS.
|
|
|
|
|
channelConfig.os = _this.os;
|
|
|
|
|
channelConfig.appVersion = _this.appVersion;
|
|
|
|
|
try {
|
|
|
|
|
var channel = channel_factory_1.default.createChannel(channelName, channelConfig);
|
|
|
|
|
_this.channels.push(channel);
|
|
|
|
|
}
|
|
|
|
|
catch (error) {
|
|
|
|
|
console.log('[BitAnalytics] ' + error.name + ': ' + error.message);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
2018-07-10 16:39:30 +09:00
|
|
|
return LogEventHandlers;
|
|
|
|
|
}());
|
|
|
|
|
exports.default = LogEventHandlers;
|
|
|
|
|
|
2018-07-25 16:17:42 +09:00
|
|
|
},{"./channel-factory":7}],16:[function(require,module,exports){
|
2018-07-10 16:39:30 +09:00
|
|
|
"use strict";
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
|
|
var LogEvent = /** @class */ (function () {
|
|
|
|
|
function LogEvent(name, params, channelNames) {
|
|
|
|
|
if (channelNames.length == 0) {
|
2018-07-13 16:15:04 +09:00
|
|
|
throw new Error('[BitAnalytics] Minimum one channel is needed.');
|
2018-07-10 16:39:30 +09:00
|
|
|
}
|
|
|
|
|
this.name = name;
|
|
|
|
|
this.params = params;
|
|
|
|
|
this.channelNames = channelNames;
|
|
|
|
|
}
|
|
|
|
|
return LogEvent;
|
|
|
|
|
}());
|
|
|
|
|
exports.default = LogEvent;
|
|
|
|
|
|
2018-07-13 16:15:04 +09:00
|
|
|
},{}]},{},[6])(6)
|
2018-07-10 16:39:30 +09:00
|
|
|
});
|