PageRenderTime 80ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/deps/js/mixpanel.js

https://github.com/ShiftFinancial/ripple-client
JavaScript | 3017 lines | 2141 code | 250 blank | 626 comment | 381 complexity | 78ccf84f17ebe11f46b2bb814d958186 MD5 | raw file
Possible License(s): 0BSD, Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. * Mixpanel JS Library v2.2.0
  3. *
  4. * Copyright 2012, Mixpanel, Inc. All Rights Reserved
  5. * http://mixpanel.com/
  6. *
  7. * Includes portions of Underscore.js
  8. * http://documentcloud.github.com/underscore/
  9. * (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
  10. * Released under the MIT License.
  11. */
  12. // ==ClosureCompiler==
  13. // @compilation_level ADVANCED_OPTIMIZATIONS
  14. // @output_file_name mixpanel-2.2.min.js
  15. // ==/ClosureCompiler==
  16. /*
  17. Will export window.mixpanel
  18. */
  19. /*
  20. SIMPLE STYLE GUIDE:
  21. this.x == public function
  22. this._x == internal - only use within this file
  23. this.__x == private - only use within the class
  24. Globals should be all caps
  25. */
  26. (function (mixpanel) {
  27. /*
  28. * Saved references to long variable names, so that closure compiler can
  29. * minimize file size.
  30. */
  31. var ArrayProto = Array.prototype
  32. , ObjProto = Object.prototype
  33. , slice = ArrayProto.slice
  34. , toString = ObjProto.toString
  35. , hasOwnProperty = ObjProto.hasOwnProperty
  36. , windowConsole = window.console
  37. , navigator = window.navigator
  38. , document = window.document
  39. , userAgent = navigator.userAgent;
  40. /*
  41. * Constants
  42. */
  43. /** @const */ var PRIMARY_INSTANCE_NAME = "mixpanel"
  44. /** @const */ , SET_QUEUE_KEY = "__mps"
  45. /** @const */ , SET_ONCE_QUEUE_KEY = "__mpso"
  46. /** @const */ , ADD_QUEUE_KEY = "__mpa"
  47. /** @const */ , APPEND_QUEUE_KEY = "__mpap"
  48. /** @const */ , SET_ACTION = "$set"
  49. /** @const */ , SET_ONCE_ACTION = "$set_once"
  50. /** @const */ , ADD_ACTION = "$add"
  51. /** @const */ , APPEND_ACTION = "$append"
  52. // This key is deprecated, but we want to check for it to see whether aliasing is allowed.
  53. /** @const */ , PEOPLE_DISTINCT_ID_KEY = "$people_distinct_id"
  54. /** @const */ , ALIAS_ID_KEY = "__alias"
  55. /** @const */ , RESERVED_PROPERTIES = [SET_QUEUE_KEY, SET_ONCE_QUEUE_KEY, ADD_QUEUE_KEY, APPEND_QUEUE_KEY, PEOPLE_DISTINCT_ID_KEY, ALIAS_ID_KEY];
  56. /*
  57. * Dynamic... constants? Is that an oxymoron?
  58. */
  59. var HTTP_PROTOCOL = (("https:" == document.location.protocol) ? "https://" : "http://")
  60. , SNIPPET_VERSION = (mixpanel && mixpanel['__SV']) || 0
  61. // http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/
  62. // https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#withCredentials
  63. , USE_XHR = (window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest())
  64. // IE<10 does not support cross-origin XHR's but script tags
  65. // with defer won't block window.onload; ENQUEUE_REQUESTS
  66. // should only be true for Opera<12
  67. , ENQUEUE_REQUESTS = !USE_XHR && (userAgent.indexOf('MSIE') == -1);
  68. /*
  69. * Closure-level globals
  70. */
  71. var _ = {}
  72. , DEBUG = false
  73. , DEFAULT_CONFIG = {
  74. "api_host": HTTP_PROTOCOL + 'api.mixpanel.com'
  75. , "cross_subdomain_cookie": true
  76. , "cookie_name": ""
  77. , "loaded": function() {}
  78. , "store_google": true
  79. , "save_referrer": true
  80. , "test": false
  81. , "verbose": false
  82. , "img": false
  83. , "track_pageview": true
  84. , "debug": false
  85. , "track_links_timeout": 300
  86. , "cookie_expiration": 365
  87. , "upgrade": false
  88. , "disable_cookie": false
  89. , "secure_cookie": false
  90. , "ip": true
  91. }
  92. , DOM_LOADED = false;
  93. // UNDERSCORE
  94. // Embed part of the Underscore Library
  95. (function() {
  96. var nativeForEach = ArrayProto.forEach,
  97. nativeIndexOf = ArrayProto.indexOf,
  98. nativeIsArray = Array.isArray,
  99. breaker = {};
  100. /**
  101. * @param {*=} obj
  102. * @param {function(...[*])=} iterator
  103. * @param {Object=} context
  104. */
  105. var each = _.each = function(obj, iterator, context) {
  106. if (obj == null) return;
  107. if (nativeForEach && obj.forEach === nativeForEach) {
  108. obj.forEach(iterator, context);
  109. } else if (obj.length === +obj.length) {
  110. for (var i = 0, l = obj.length; i < l; i++) {
  111. if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
  112. }
  113. } else {
  114. for (var key in obj) {
  115. if (hasOwnProperty.call(obj, key)) {
  116. if (iterator.call(context, obj[key], key, obj) === breaker) return;
  117. }
  118. }
  119. }
  120. };
  121. _.extend = function(obj) {
  122. each(slice.call(arguments, 1), function(source) {
  123. for (var prop in source) {
  124. if (source[prop] !== void 0) obj[prop] = source[prop];
  125. }
  126. });
  127. return obj;
  128. };
  129. _.isArray = nativeIsArray || function(obj) {
  130. return toString.call(obj) === '[object Array]';
  131. };
  132. // from a comment on http://dbj.org/dbj/?p=286
  133. // fails on only one very rare and deliberate custom object:
  134. // var bomb = { toString : undefined, valueOf: function(o) { return "function BOMBA!"; }};
  135. _.isFunction = function (f) {
  136. try {
  137. return /^\s*\bfunction\b/.test(f);
  138. } catch (x) {
  139. return false;
  140. }
  141. };
  142. _.isArguments = function(obj) {
  143. return !!(obj && hasOwnProperty.call(obj, 'callee'));
  144. };
  145. _.toArray = function(iterable) {
  146. if (!iterable) return [];
  147. if (iterable.toArray) return iterable.toArray();
  148. if (_.isArray(iterable)) return slice.call(iterable);
  149. if (_.isArguments(iterable)) return slice.call(iterable);
  150. return _.values(iterable);
  151. };
  152. _.values = function(obj) {
  153. var results = [];
  154. if (obj == null) return results;
  155. each(obj, function(value) {
  156. results[results.length] = value;
  157. });
  158. return results;
  159. };
  160. _.identity = function(value) {
  161. return value;
  162. };
  163. _.include = function(obj, target) {
  164. var found = false;
  165. if (obj == null) return found;
  166. if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
  167. each(obj, function(value) {
  168. if (found || (found = (value === target))) { return breaker; }
  169. });
  170. return found;
  171. };
  172. _.includes = function(str, needle) {
  173. return str.indexOf(needle) !== -1;
  174. };
  175. })();
  176. // Underscore Addons
  177. _.inherit = function(subclass, superclass) {
  178. subclass.prototype = new superclass();
  179. subclass.prototype.constructor = subclass;
  180. subclass.superclass = superclass.prototype;
  181. return subclass;
  182. };
  183. _.isObject = function(obj) {
  184. return (obj === Object(obj) && !_.isArray(obj));
  185. };
  186. _.isEmptyObject = function(obj) {
  187. if (_.isObject(obj)) {
  188. for (var key in obj) {
  189. if (hasOwnProperty.call(obj, key)) {
  190. return false;
  191. }
  192. }
  193. return true;
  194. }
  195. return false;
  196. };
  197. _.isUndefined = function(obj) {
  198. return obj === void 0;
  199. };
  200. _.isString = function(obj) {
  201. return toString.call(obj) == '[object String]';
  202. };
  203. _.isDate = function(obj) {
  204. return toString.call(obj) == '[object Date]';
  205. };
  206. _.isNumber = function(obj) {
  207. return toString.call(obj) == '[object Number]';
  208. };
  209. _.encodeDates = function(obj) {
  210. _.each(obj, function(v, k) {
  211. if (_.isDate(v)) {
  212. obj[k] = _.formatDate(v);
  213. } else if (_.isObject(v)) {
  214. obj[k] = _.encodeDates(v); // recurse
  215. }
  216. });
  217. return obj;
  218. };
  219. _.formatDate = function(d) {
  220. // YYYY-MM-DDTHH:MM:SS in UTC
  221. function pad(n) {return n < 10 ? '0' + n : n}
  222. return d.getUTCFullYear() + '-'
  223. + pad(d.getUTCMonth() + 1) + '-'
  224. + pad(d.getUTCDate()) + 'T'
  225. + pad(d.getUTCHours()) + ':'
  226. + pad(d.getUTCMinutes()) + ':'
  227. + pad(d.getUTCSeconds());
  228. };
  229. _.strip_empty_properties = function(p) {
  230. var ret = {};
  231. _.each(p, function(v, k) {
  232. if (_.isString(v) && v.length > 0) { ret[k] = v; }
  233. });
  234. return ret;
  235. };
  236. /*
  237. * this function returns a copy of object after truncating it. If
  238. * passed an Array or Object it will iterate through obj and
  239. * truncate all the values recursively.
  240. */
  241. _.truncate = function(obj, length) {
  242. var ret;
  243. if (typeof(obj) === "string") {
  244. ret = obj.slice(0, length);
  245. } else if (_.isArray(obj)) {
  246. ret = [];
  247. _.each(obj, function(val) {
  248. ret.push(_.truncate(val, length));
  249. });
  250. } else if (_.isObject(obj)) {
  251. ret = {};
  252. _.each(obj, function(val, key) {
  253. ret[key] = _.truncate(val, length);
  254. });
  255. } else {
  256. ret = obj;
  257. }
  258. return ret;
  259. };
  260. _.JSONEncode = (function() {
  261. return function(mixed_val) {
  262. var indent;
  263. var value = mixed_val;
  264. var i;
  265. var quote = function (string) {
  266. var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
  267. var meta = { // table of character substitutions
  268. '\b': '\\b',
  269. '\t': '\\t',
  270. '\n': '\\n',
  271. '\f': '\\f',
  272. '\r': '\\r',
  273. '"' : '\\"',
  274. '\\': '\\\\'
  275. };
  276. escapable.lastIndex = 0;
  277. return escapable.test(string) ?
  278. '"' + string.replace(escapable, function (a) {
  279. var c = meta[a];
  280. return typeof c === 'string' ? c :
  281. '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
  282. }) + '"' :
  283. '"' + string + '"';
  284. };
  285. var str = function(key, holder) {
  286. var gap = '';
  287. var indent = ' ';
  288. var i = 0; // The loop counter.
  289. var k = ''; // The member key.
  290. var v = ''; // The member value.
  291. var length = 0;
  292. var mind = gap;
  293. var partial = [];
  294. var value = holder[key];
  295. // If the value has a toJSON method, call it to obtain a replacement value.
  296. if (value && typeof value === 'object' &&
  297. typeof value.toJSON === 'function') {
  298. value = value.toJSON(key);
  299. }
  300. // What happens next depends on the value's type.
  301. switch (typeof value) {
  302. case 'string':
  303. return quote(value);
  304. case 'number':
  305. // JSON numbers must be finite. Encode non-finite numbers as null.
  306. return isFinite(value) ? String(value) : 'null';
  307. case 'boolean':
  308. case 'null':
  309. // If the value is a boolean or null, convert it to a string. Note:
  310. // typeof null does not produce 'null'. The case is included here in
  311. // the remote chance that this gets fixed someday.
  312. return String(value);
  313. case 'object':
  314. // If the type is 'object', we might be dealing with an object or an array or
  315. // null.
  316. // Due to a specification blunder in ECMAScript, typeof null is 'object',
  317. // so watch out for that case.
  318. if (!value) {
  319. return 'null';
  320. }
  321. // Make an array to hold the partial results of stringifying this object value.
  322. gap += indent;
  323. partial = [];
  324. // Is the value an array?
  325. if (toString.apply(value) === '[object Array]') {
  326. // The value is an array. Stringify every element. Use null as a placeholder
  327. // for non-JSON values.
  328. length = value.length;
  329. for (i = 0; i < length; i += 1) {
  330. partial[i] = str(i, value) || 'null';
  331. }
  332. // Join all of the elements together, separated with commas, and wrap them in
  333. // brackets.
  334. v = partial.length === 0 ? '[]' :
  335. gap ? '[\n' + gap +
  336. partial.join(',\n' + gap) + '\n' +
  337. mind + ']' :
  338. '[' + partial.join(',') + ']';
  339. gap = mind;
  340. return v;
  341. }
  342. // Iterate through all of the keys in the object.
  343. for (k in value) {
  344. if (hasOwnProperty.call(value, k)) {
  345. v = str(k, value);
  346. if (v) {
  347. partial.push(quote(k) + (gap ? ': ' : ':') + v);
  348. }
  349. }
  350. }
  351. // Join all of the member texts together, separated with commas,
  352. // and wrap them in braces.
  353. v = partial.length === 0 ? '{}' :
  354. gap ? '{' + partial.join(',') + '' +
  355. mind + '}' : '{' + partial.join(',') + '}';
  356. gap = mind;
  357. return v;
  358. }
  359. };
  360. // Make a fake root object containing our value under the key of ''.
  361. // Return the result of stringifying the value.
  362. return str('', {
  363. '': value
  364. });
  365. };
  366. })();
  367. _.JSONDecode = (function() { // https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js
  368. var at, // The index of the current character
  369. ch, // The current character
  370. escapee = {
  371. '"': '"',
  372. '\\': '\\',
  373. '/': '/',
  374. 'b': '\b',
  375. 'f': '\f',
  376. 'n': '\n',
  377. 'r': '\r',
  378. 't': '\t'
  379. },
  380. text,
  381. error = function (m) {
  382. throw {
  383. name: 'SyntaxError',
  384. message: m,
  385. at: at,
  386. text: text
  387. };
  388. },
  389. next = function (c) {
  390. // If a c parameter is provided, verify that it matches the current character.
  391. if (c && c !== ch) {
  392. error("Expected '" + c + "' instead of '" + ch + "'");
  393. }
  394. // Get the next character. When there are no more characters,
  395. // return the empty string.
  396. ch = text.charAt(at);
  397. at += 1;
  398. return ch;
  399. },
  400. number = function () {
  401. // Parse a number value.
  402. var number,
  403. string = '';
  404. if (ch === '-') {
  405. string = '-';
  406. next('-');
  407. }
  408. while (ch >= '0' && ch <= '9') {
  409. string += ch;
  410. next();
  411. }
  412. if (ch === '.') {
  413. string += '.';
  414. while (next() && ch >= '0' && ch <= '9') {
  415. string += ch;
  416. }
  417. }
  418. if (ch === 'e' || ch === 'E') {
  419. string += ch;
  420. next();
  421. if (ch === '-' || ch === '+') {
  422. string += ch;
  423. next();
  424. }
  425. while (ch >= '0' && ch <= '9') {
  426. string += ch;
  427. next();
  428. }
  429. }
  430. number = +string;
  431. if (!isFinite(number)) {
  432. error("Bad number");
  433. } else {
  434. return number;
  435. }
  436. },
  437. string = function () {
  438. // Parse a string value.
  439. var hex,
  440. i,
  441. string = '',
  442. uffff;
  443. // When parsing for string values, we must look for " and \ characters.
  444. if (ch === '"') {
  445. while (next()) {
  446. if (ch === '"') {
  447. next();
  448. return string;
  449. }
  450. if (ch === '\\') {
  451. next();
  452. if (ch === 'u') {
  453. uffff = 0;
  454. for (i = 0; i < 4; i += 1) {
  455. hex = parseInt(next(), 16);
  456. if (!isFinite(hex)) {
  457. break;
  458. }
  459. uffff = uffff * 16 + hex;
  460. }
  461. string += String.fromCharCode(uffff);
  462. } else if (typeof escapee[ch] === 'string') {
  463. string += escapee[ch];
  464. } else {
  465. break;
  466. }
  467. } else {
  468. string += ch;
  469. }
  470. }
  471. }
  472. error("Bad string");
  473. },
  474. white = function () {
  475. // Skip whitespace.
  476. while (ch && ch <= ' ') {
  477. next();
  478. }
  479. },
  480. word = function () {
  481. // true, false, or null.
  482. switch (ch) {
  483. case 't':
  484. next('t');
  485. next('r');
  486. next('u');
  487. next('e');
  488. return true;
  489. case 'f':
  490. next('f');
  491. next('a');
  492. next('l');
  493. next('s');
  494. next('e');
  495. return false;
  496. case 'n':
  497. next('n');
  498. next('u');
  499. next('l');
  500. next('l');
  501. return null;
  502. }
  503. error("Unexpected '" + ch + "'");
  504. },
  505. value, // Placeholder for the value function.
  506. array = function () {
  507. // Parse an array value.
  508. var array = [];
  509. if (ch === '[') {
  510. next('[');
  511. white();
  512. if (ch === ']') {
  513. next(']');
  514. return array; // empty array
  515. }
  516. while (ch) {
  517. array.push(value());
  518. white();
  519. if (ch === ']') {
  520. next(']');
  521. return array;
  522. }
  523. next(',');
  524. white();
  525. }
  526. }
  527. error("Bad array");
  528. },
  529. object = function () {
  530. // Parse an object value.
  531. var key,
  532. object = {};
  533. if (ch === '{') {
  534. next('{');
  535. white();
  536. if (ch === '}') {
  537. next('}');
  538. return object; // empty object
  539. }
  540. while (ch) {
  541. key = string();
  542. white();
  543. next(':');
  544. if (Object.hasOwnProperty.call(object, key)) {
  545. error('Duplicate key "' + key + '"');
  546. }
  547. object[key] = value();
  548. white();
  549. if (ch === '}') {
  550. next('}');
  551. return object;
  552. }
  553. next(',');
  554. white();
  555. }
  556. }
  557. error("Bad object");
  558. };
  559. value = function () {
  560. // Parse a JSON value. It could be an object, an array, a string,
  561. // a number, or a word.
  562. white();
  563. switch (ch) {
  564. case '{':
  565. return object();
  566. case '[':
  567. return array();
  568. case '"':
  569. return string();
  570. case '-':
  571. return number();
  572. default:
  573. return ch >= '0' && ch <= '9' ? number() : word();
  574. }
  575. };
  576. // Return the json_parse function. It will have access to all of the
  577. // above functions and variables.
  578. return function (source) {
  579. var result;
  580. text = source;
  581. at = 0;
  582. ch = ' ';
  583. result = value();
  584. white();
  585. if (ch) {
  586. error("Syntax error");
  587. }
  588. return result;
  589. };
  590. })();
  591. _.base64Encode = function(data) {
  592. var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  593. var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc="", tmp_arr = [];
  594. if (!data) {
  595. return data;
  596. }
  597. data = _.utf8Encode(data);
  598. do { // pack three octets into four hexets
  599. o1 = data.charCodeAt(i++);
  600. o2 = data.charCodeAt(i++);
  601. o3 = data.charCodeAt(i++);
  602. bits = o1<<16 | o2<<8 | o3;
  603. h1 = bits>>18 & 0x3f;
  604. h2 = bits>>12 & 0x3f;
  605. h3 = bits>>6 & 0x3f;
  606. h4 = bits & 0x3f;
  607. // use hexets to index into b64, and append result to encoded string
  608. tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
  609. } while (i < data.length);
  610. enc = tmp_arr.join('');
  611. switch( data.length % 3 ){
  612. case 1:
  613. enc = enc.slice(0, -2) + '==';
  614. break;
  615. case 2:
  616. enc = enc.slice(0, -1) + '=';
  617. break;
  618. }
  619. return enc;
  620. };
  621. _.utf8Encode = function(string) {
  622. string = (string+'').replace(/\r\n/g, "\n").replace(/\r/g, "\n");
  623. var utftext = "",
  624. start,
  625. end;
  626. var stringl = 0,
  627. n;
  628. start = end = 0;
  629. stringl = string.length;
  630. for (n = 0; n < stringl; n++) {
  631. var c1 = string.charCodeAt(n);
  632. var enc = null;
  633. if (c1 < 128) {
  634. end++;
  635. } else if((c1 > 127) && (c1 < 2048)) {
  636. enc = String.fromCharCode((c1 >> 6) | 192, (c1 & 63) | 128);
  637. } else {
  638. enc = String.fromCharCode((c1 >> 12) | 224, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128);
  639. }
  640. if (enc !== null) {
  641. if (end > start) {
  642. utftext += string.substring(start, end);
  643. }
  644. utftext += enc;
  645. start = end = n+1;
  646. }
  647. }
  648. if (end > start) {
  649. utftext += string.substring(start, string.length);
  650. }
  651. return utftext;
  652. };
  653. _.UUID = (function() {
  654. // Time/ticks information
  655. // 1*new Date() is a cross browser version of Date.now()
  656. var T = function() {
  657. var d = 1*new Date()
  658. , i = 0;
  659. // this while loop figures how many browser ticks go by
  660. // before 1*new Date() returns a new number, ie the amount
  661. // of ticks that go by per millisecond
  662. while (d == 1*new Date()) { i++; }
  663. return d.toString(16) + i.toString(16);
  664. };
  665. // Math.Random entropy
  666. var R = function() {
  667. return Math.random().toString(16).replace('.','');
  668. };
  669. // User agent entropy
  670. // This function takes the user agent string, and then xors
  671. // together each sequence of 8 bytes. This produces a final
  672. // sequence of 8 bytes which it returns as hex.
  673. var UA = function(n) {
  674. var ua = userAgent, i, ch, buffer = [], ret = 0;
  675. function xor(result, byte_array) {
  676. var j, tmp = 0;
  677. for (j = 0; j < byte_array.length; j++) {
  678. tmp |= (buffer[j] << j*8);
  679. }
  680. return result ^ tmp;
  681. }
  682. for (i = 0; i < ua.length; i++) {
  683. ch = ua.charCodeAt(i);
  684. buffer.unshift(ch & 0xFF);
  685. if (buffer.length >= 4) {
  686. ret = xor(ret, buffer);
  687. buffer = [];
  688. }
  689. }
  690. if (buffer.length > 0) { ret = xor(ret, buffer); }
  691. return ret.toString(16);
  692. };
  693. return function() {
  694. var se = (screen.height*screen.width).toString(16);
  695. return (T()+"-"+R()+"-"+UA()+"-"+se+"-"+T());
  696. };
  697. })();
  698. // _.isBlockedUA()
  699. // This is to block various web spiders from executing our JS and
  700. // sending false tracking data
  701. _.isBlockedUA = function() {
  702. var a = userAgent;
  703. if (/(google web preview|baiduspider|yandexbot)/i.test(a)) {
  704. return true;
  705. }
  706. return false;
  707. };
  708. /**
  709. * @param {Object=} formdata
  710. * @param {string=} arg_separator
  711. */
  712. _.HTTPBuildQuery = function(formdata, arg_separator) {
  713. var key, use_val, use_key, tmp_arr = [];
  714. if (typeof(arg_separator) === "undefined") {
  715. arg_separator = '&';
  716. }
  717. _.each(formdata, function(val, key) {
  718. use_val = encodeURIComponent(val.toString());
  719. use_key = encodeURIComponent(key);
  720. tmp_arr[tmp_arr.length] = use_key + '=' + use_val;
  721. });
  722. return tmp_arr.join(arg_separator);
  723. };
  724. _.getQueryParam = function(url, param) {
  725. // Expects a raw URL
  726. param = param.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
  727. var regexS = "[\\?&]" + param + "=([^&#]*)",
  728. regex = new RegExp( regexS ),
  729. results = regex.exec(url);
  730. if (results === null || (results && typeof(results[1]) !== 'string' && results[1].length)) {
  731. return '';
  732. } else {
  733. return decodeURIComponent(results[1]).replace(/\+/g, ' ');
  734. }
  735. };
  736. // _.cookie
  737. // Methods partially borrowed from quirksmode.org/js/cookies.html
  738. _.cookie = {
  739. get: function(name) {
  740. var nameEQ = name + "=";
  741. var ca = document.cookie.split(';');
  742. for(var i=0;i < ca.length;i++) {
  743. var c = ca[i];
  744. while (c.charAt(0)==' ') c = c.substring(1,c.length);
  745. if (c.indexOf(nameEQ) == 0) return decodeURIComponent(c.substring(nameEQ.length,c.length));
  746. }
  747. return null;
  748. },
  749. parse: function(name) {
  750. var cookie;
  751. try {
  752. cookie = _.JSONDecode(_.cookie.get(name)) || {};
  753. } catch (err) {}
  754. return cookie;
  755. },
  756. set: function(name, value, days, cross_subdomain, is_secure) {
  757. var cdomain = "", expires = "", secure = "";
  758. if (cross_subdomain) {
  759. var matches = document.location.hostname.match(/[a-z0-9][a-z0-9\-]+\.[a-z\.]{2,6}$/i)
  760. , domain = matches ? matches[0] : '';
  761. cdomain = ((domain) ? "; domain=." + domain : "");
  762. }
  763. if (days) {
  764. var date = new Date();
  765. date.setTime(date.getTime()+(days*24*60*60*1000));
  766. expires = "; expires=" + date.toGMTString();
  767. }
  768. if (is_secure) {
  769. secure = "; secure";
  770. }
  771. document.cookie = name + "=" + encodeURIComponent(value) + expires + "; path=/" + cdomain + secure;
  772. },
  773. remove: function(name, cross_subdomain) {
  774. _.cookie.set(name, '', -1, cross_subdomain);
  775. }
  776. };
  777. _.register_event = (function() {
  778. // written by Dean Edwards, 2005
  779. // with input from Tino Zijdel - crisp@xs4all.nl
  780. // with input from Carl Sverre - mail@carlsverre.com
  781. // with input from Mixpanel
  782. // http://dean.edwards.name/weblog/2005/10/add-event/
  783. // https://gist.github.com/1930440
  784. /**
  785. * @param {Object} element
  786. * @param {string} type
  787. * @param {function(...[*])} handler
  788. * @param {boolean=} oldSchool
  789. */
  790. var register_event = function(element, type, handler, oldSchool) {
  791. if (!element) {
  792. console.error("No valid element provided to register_event");
  793. return;
  794. }
  795. if (element.addEventListener && !oldSchool) {
  796. element.addEventListener(type, handler, false);
  797. } else {
  798. var ontype = 'on' + type;
  799. var old_handler = element[ontype]; // can be undefined
  800. element[ontype] = makeHandler(element, handler, old_handler);
  801. }
  802. };
  803. function makeHandler(element, new_handler, old_handlers) {
  804. var handler = function(event) {
  805. event = event || fixEvent(window.event);
  806. // this basically happens in firefox whenever another script
  807. // overwrites the onload callback and doesn't pass the event
  808. // object to previously defined callbacks. All the browsers
  809. // that don't define window.event implement addEventListener
  810. // so the dom_loaded handler will still be fired as usual.
  811. if (!event) { return undefined; }
  812. var ret = true;
  813. var old_result, new_result;
  814. if (_.isFunction(old_handlers)) {
  815. old_result = old_handlers(event);
  816. }
  817. new_result = new_handler.call(element, event);
  818. if ((false === old_result) || (false === new_result)) {
  819. ret = false;
  820. }
  821. return ret;
  822. };
  823. return handler;
  824. };
  825. function fixEvent(event) {
  826. if (event) {
  827. event.preventDefault = fixEvent.preventDefault;
  828. event.stopPropagation = fixEvent.stopPropagation;
  829. }
  830. return event;
  831. };
  832. fixEvent.preventDefault = function() {
  833. this.returnValue = false;
  834. };
  835. fixEvent.stopPropagation = function() {
  836. this.cancelBubble = true;
  837. };
  838. return register_event;
  839. })();
  840. _.dom_query = (function() {
  841. /* document.getElementsBySelector(selector)
  842. - returns an array of element objects from the current document
  843. matching the CSS selector. Selectors can contain element names,
  844. class names and ids and can be nested. For example:
  845. elements = document.getElementsBySelector('div#main p a.external')
  846. Will return an array of all 'a' elements with 'external' in their
  847. class attribute that are contained inside 'p' elements that are
  848. contained inside the 'div' element which has id="main"
  849. New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
  850. See http://www.w3.org/TR/css3-selectors/#attribute-selectors
  851. Version 0.4 - Simon Willison, March 25th 2003
  852. -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
  853. -- Opera 7 fails
  854. Version 0.5 - Carl Sverre, Jan 7th 2013
  855. -- Now uses jQuery-esque `hasClass` for testing class name
  856. equality. This fixes a bug related to '-' characters being
  857. considered not part of a 'word' in regex.
  858. */
  859. function getAllChildren(e) {
  860. // Returns all children of element. Workaround required for IE5/Windows. Ugh.
  861. return e.all ? e.all : e.getElementsByTagName('*');
  862. }
  863. var bad_whitespace = /[\t\r\n]/g;
  864. function hasClass(elem, selector) {
  865. var className = " " + selector + " ";
  866. return ((" " + elem.className + " ").replace(bad_whitespace, " ").indexOf(className) >= 0);
  867. }
  868. function getElementsBySelector(selector) {
  869. // Attempt to fail gracefully in lesser browsers
  870. if (!document.getElementsByTagName) {
  871. return new Array();
  872. }
  873. // Split selector in to tokens
  874. var tokens = selector.split(' ');
  875. var token;
  876. var currentContext = new Array(document);
  877. for (var i = 0; i < tokens.length; i++) {
  878. token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');
  879. if (token.indexOf('#') > -1) {
  880. // Token is an ID selector
  881. var bits = token.split('#');
  882. var tagName = bits[0];
  883. var id = bits[1];
  884. var element = document.getElementById(id);
  885. if (!element || (tagName && element.nodeName.toLowerCase() != tagName)) {
  886. // element not found or tag with that ID not found, return false
  887. return new Array();
  888. }
  889. // Set currentContext to contain just this element
  890. currentContext = new Array(element);
  891. continue; // Skip to next token
  892. }
  893. if (token.indexOf('.') > -1) {
  894. // Token contains a class selector
  895. var bits = token.split('.');
  896. var tagName = bits[0];
  897. var className = bits[1];
  898. if (!tagName) {
  899. tagName = '*';
  900. }
  901. // Get elements matching tag, filter them for class selector
  902. var found = new Array;
  903. var foundCount = 0;
  904. for (var h = 0; h < currentContext.length; h++) {
  905. var elements;
  906. if (tagName == '*') {
  907. elements = getAllChildren(currentContext[h]);
  908. } else {
  909. elements = currentContext[h].getElementsByTagName(tagName);
  910. }
  911. for (var j = 0; j < elements.length; j++) {
  912. found[foundCount++] = elements[j];
  913. }
  914. }
  915. currentContext = new Array;
  916. var currentContextIndex = 0;
  917. for (var k = 0; k < found.length; k++) {
  918. if (found[k].className
  919. && _.isString(found[k].className) // some SVG elements have classNames which are not strings
  920. && hasClass(found[k], className)
  921. ) {
  922. currentContext[currentContextIndex++] = found[k];
  923. }
  924. }
  925. continue; // Skip to next token
  926. }
  927. // Code to deal with attribute selectors
  928. if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
  929. var tagName = RegExp.$1;
  930. var attrName = RegExp.$2;
  931. var attrOperator = RegExp.$3;
  932. var attrValue = RegExp.$4;
  933. if (!tagName) {
  934. tagName = '*';
  935. }
  936. // Grab all of the tagName elements within current context
  937. var found = new Array;
  938. var foundCount = 0;
  939. for (var h = 0; h < currentContext.length; h++) {
  940. var elements;
  941. if (tagName == '*') {
  942. elements = getAllChildren(currentContext[h]);
  943. } else {
  944. elements = currentContext[h].getElementsByTagName(tagName);
  945. }
  946. for (var j = 0; j < elements.length; j++) {
  947. found[foundCount++] = elements[j];
  948. }
  949. }
  950. currentContext = new Array;
  951. var currentContextIndex = 0;
  952. var checkFunction; // This function will be used to filter the elements
  953. switch (attrOperator) {
  954. case '=': // Equality
  955. checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
  956. break;
  957. case '~': // Match one of space seperated words
  958. checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
  959. break;
  960. case '|': // Match start with value followed by optional hyphen
  961. checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
  962. break;
  963. case '^': // Match starts with value
  964. checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
  965. break;
  966. case '$': // Match ends with value - fails with "Warning" in Opera 7
  967. checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
  968. break;
  969. case '*': // Match ends with value
  970. checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
  971. break;
  972. default :
  973. // Just test for existence of attribute
  974. checkFunction = function(e) { return e.getAttribute(attrName); };
  975. }
  976. currentContext = new Array;
  977. currentContextIndex = 0;
  978. for (var k = 0; k < found.length; k++) {
  979. if (checkFunction(found[k])) {
  980. currentContext[currentContextIndex++] = found[k];
  981. }
  982. }
  983. // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
  984. continue; // Skip to next token
  985. }
  986. // If we get here, token is JUST an element (not a class or ID selector)
  987. tagName = token;
  988. var found = new Array;
  989. var foundCount = 0;
  990. for (var h = 0; h < currentContext.length; h++) {
  991. var elements = currentContext[h].getElementsByTagName(tagName);
  992. for (var j = 0; j < elements.length; j++) {
  993. found[foundCount++] = elements[j];
  994. }
  995. }
  996. currentContext = found;
  997. }
  998. return currentContext;
  999. };
  1000. return getElementsBySelector;
  1001. })();
  1002. _.info = {
  1003. campaignParams: function() {
  1004. var campaign_keywords = 'utm_source utm_medium utm_campaign utm_content utm_term'.split(' ')
  1005. , kw = ''
  1006. , params = {};
  1007. _.each(campaign_keywords, function(kwkey) {
  1008. kw = _.getQueryParam(document.URL, kwkey);
  1009. if (kw.length) {
  1010. params[kwkey] = kw;
  1011. }
  1012. });
  1013. return params;
  1014. },
  1015. searchEngine: function(referrer) {
  1016. if (referrer.search('https?://(.*)google.([^/?]*)') === 0) {
  1017. return 'google';
  1018. } else if (referrer.search('https?://(.*)bing.com') === 0) {
  1019. return 'bing';
  1020. } else if (referrer.search('https?://(.*)yahoo.com') === 0) {
  1021. return 'yahoo';
  1022. } else if (referrer.search('https?://(.*)duckduckgo.com') === 0) {
  1023. return 'duckduckgo';
  1024. } else {
  1025. return null;
  1026. }
  1027. },
  1028. searchInfo: function(referrer) {
  1029. var search = _.info.searchEngine(referrer)
  1030. , param = (search != "yahoo") ? "q" : "p"
  1031. , ret = {};
  1032. if (search !== null) {
  1033. ret["$search_engine"] = search;
  1034. var keyword = _.getQueryParam(referrer, param);
  1035. if (keyword.length) {
  1036. ret["mp_keyword"] = keyword;
  1037. }
  1038. }
  1039. return ret;
  1040. },
  1041. /**
  1042. * This function detects which browser is running this script.
  1043. * The order of the checks are important since many user agents
  1044. * include key words used in later checks.
  1045. */
  1046. browser: function() {
  1047. var ua = userAgent
  1048. , vend = navigator.vendor || ''; // vendor is undefined for at least IE9
  1049. if (window.opera) {
  1050. if (_.includes(ua, "Mini")) {
  1051. return "Opera Mini";
  1052. }
  1053. return "Opera";
  1054. } else if (/(BlackBerry|PlayBook|BB10)/i.test(ua)) {
  1055. return 'BlackBerry';
  1056. } else if (_.includes(ua, "Chrome")) {
  1057. return "Chrome";
  1058. } else if (_.includes(vend, "Apple")) {
  1059. if (_.includes(ua, "Mobile")) {
  1060. return "Mobile Safari";
  1061. }
  1062. return "Safari";
  1063. } else if (_.includes(ua, "Android")) {
  1064. return "Android Mobile";
  1065. } else if (_.includes(ua, "Konqueror")) {
  1066. return "Konqueror";
  1067. } else if (_.includes(ua, "Firefox")) {
  1068. return "Firefox";
  1069. } else if (_.includes(ua, "MSIE")) {
  1070. return "Internet Explorer";
  1071. } else if (_.includes(ua, "Gecko")) {
  1072. return "Mozilla";
  1073. } else {
  1074. return "";
  1075. }
  1076. },
  1077. os: function() {
  1078. var a = userAgent;
  1079. if (/Windows/i.test(a)) {
  1080. if (/Phone/.test(a)) { return 'Windows Mobile'; }
  1081. return 'Windows';
  1082. } else if (/(iPhone|iPad|iPod)/.test(a)) {
  1083. return 'iOS';
  1084. } else if (/Android/.test(a)) {
  1085. return 'Android';
  1086. } else if (/(BlackBerry|PlayBook|BB10)/i.test(a)) {
  1087. return 'BlackBerry';
  1088. } else if (/Mac/i.test(a)) {
  1089. return 'Mac OS X';
  1090. } else if (/Linux/.test(a)) {
  1091. return 'Linux';
  1092. } else {
  1093. return '';
  1094. }
  1095. },
  1096. device: function() {
  1097. var a = userAgent;
  1098. if (/iPhone/.test(a)) {
  1099. return 'iPhone';
  1100. } else if (/iPad/.test(a)) {
  1101. return 'iPad';
  1102. } else if (/iPod/.test(a)) {
  1103. return 'iPod Touch';
  1104. } else if (/(BlackBerry|PlayBook|BB10)/i.test(a)) {
  1105. return 'BlackBerry';
  1106. } else if (/Windows Phone/i.test(a)) {
  1107. return 'Windows Phone';
  1108. } else if (/Android/.test(a)) {
  1109. return 'Android';
  1110. } else {
  1111. return '';
  1112. }
  1113. },
  1114. referringDomain: function(referrer) {
  1115. var split = referrer.split("/");
  1116. if (split.length >= 3) {
  1117. return split[2];
  1118. }
  1119. return "";
  1120. },
  1121. properties: function() {
  1122. return _.strip_empty_properties({
  1123. '$os': _.info.os(),
  1124. '$browser': _.info.browser(),
  1125. '$referrer': document.referrer,
  1126. '$referring_domain': _.info.referringDomain(document.referrer),
  1127. '$device': _.info.device(),
  1128. 'mp_lib': 'web'
  1129. });
  1130. },
  1131. people_properties: function() {
  1132. return _.strip_empty_properties({
  1133. '$os': _.info.os(),
  1134. '$browser': _.info.browser()
  1135. });
  1136. },
  1137. pageviewInfo: function(page) {
  1138. return _.strip_empty_properties({
  1139. 'mp_page': page
  1140. , 'mp_referrer': document.referrer
  1141. , 'mp_browser': _.info.browser()
  1142. , 'mp_platform': _.info.os()
  1143. });
  1144. }
  1145. };
  1146. // Console override
  1147. var console = {
  1148. /** @type {function(...[*])} */
  1149. log: function() {
  1150. if (DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
  1151. try {
  1152. windowConsole.log.apply(windowConsole, arguments);
  1153. } catch(err) {
  1154. _.each(arguments, function(arg) {
  1155. windowConsole.log(arg);
  1156. });
  1157. }
  1158. }
  1159. },
  1160. /** @type {function(...[*])} */
  1161. error: function() {
  1162. if (DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
  1163. var args = ["Mixpanel error:"].concat(_.toArray(arguments));
  1164. try {
  1165. windowConsole.error.apply(windowConsole, args);
  1166. } catch(err) {
  1167. _.each(args, function(arg) {
  1168. windowConsole.error(arg);
  1169. });
  1170. }
  1171. }
  1172. },
  1173. /** @type {function(...[*])} */
  1174. critical: function() {
  1175. if (!_.isUndefined(windowConsole) && windowConsole) {
  1176. var args = ["Mixpanel error:"].concat(_.toArray(arguments));
  1177. try {
  1178. windowConsole.error.apply(windowConsole, args);
  1179. } catch(err) {
  1180. _.each(args, function(arg) {
  1181. windowConsole.error(arg);
  1182. });
  1183. }
  1184. }
  1185. }
  1186. };
  1187. /**
  1188. * DomTracker Object
  1189. * @constructor
  1190. */
  1191. var DomTracker = function() {};
  1192. // interface
  1193. DomTracker.prototype.create_properties = function() {};
  1194. DomTracker.prototype.event_handler = function() {};
  1195. DomTracker.prototype.after_track_handler = function() {};
  1196. DomTracker.prototype.init = function(mixpanel_instance) {
  1197. this.mp = mixpanel_instance;
  1198. return this;
  1199. };
  1200. /**
  1201. * @param {string} query
  1202. * @param {string} event_name
  1203. * @param {Object=} properties
  1204. * @param {function(...[*])=} user_callback
  1205. */
  1206. DomTracker.prototype.track = function(query, event_name, properties, user_callback) {
  1207. var that = this
  1208. , elements = _.dom_query(query);
  1209. if (elements.length == 0) {
  1210. console.error("The DOM query (" + query + ") returned 0 elements");
  1211. return;
  1212. }
  1213. _.each(elements, function(element) {
  1214. _.register_event(element, this.override_event, function(e) {
  1215. var options = {}
  1216. , props = that.create_properties(properties, this)
  1217. , timeout = that.mp.get_config("track_links_timeout");
  1218. that.event_handler(e, this, options);
  1219. // in case the mixpanel servers don't get back to us in time
  1220. window.setTimeout(that.track_callback(user_callback, props, options, true), timeout);
  1221. // fire the tracking event
  1222. that.mp.track(event_name, props, that.track_callback(user_callback, props, options));
  1223. });
  1224. }, this);
  1225. return true;
  1226. };
  1227. /**
  1228. * @param {function(...[*])} user_callback
  1229. * @param {Object} props
  1230. * @param {boolean=} timeout_occured
  1231. */
  1232. DomTracker.prototype.track_callback = function(user_callback, props, options, timeout_occured) {
  1233. timeout_occured = timeout_occured || false;

Large files files are truncated, but you can click here to view the full file