Created
February 5, 2014 21:19
-
-
Save sgammon/8833331 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*** | |
* | |
* | |
* /$$ /$$ /$$$$$ /$$$$$$ | |
* | $$ /$$/ |__ $$ /$$__ $$ | |
* | $$ /$$/ /$$$$$$ /$$$$$$ /$$$$$$$ | $$| $$ \__/ | |
* | $$$$$/ /$$__ $$ /$$__ $$| $$__ $$ | $$| $$$$$$ | |
* | $$ $$ | $$$$$$$$| $$$$$$$$| $$ \ $$ /$$ | $$ \____ $$ | |
* | $$\ $$ | $$_____/| $$_____/| $$ | $$ | $$ | $$ /$$ \ $$ | |
* | $$ \ $$| $$$$$$$| $$$$$$$| $$ | $$ | $$$$$$/| $$$$$$/ | |
* |__/ \__/ \_______/ \_______/|__/ |__/ \______/ \______/ | |
* | |
* made with love <3 in san francisco | |
* ============ v 2.3.2 =========== | |
*/ | |
(function (context) { | |
var debug = /** @const */ false, | |
kn = context['Keen'] || {}, _eq = kn['_eq'] || [], _cf = kn['_cf'] || {}, _gp = kn['_gp'] || {}, | |
log = /** @const */ debug ? context['console'] ? context['console'].log : function () {} : function () {}, | |
cx = /** @const */ /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, | |
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, | |
rep, gap, indent, meta = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\' }, // table of character substitutions | |
/** | |
* Format integers to have at least two digits. | |
* | |
* @param {Number} n Integer to add digits to. | |
* @returns {Number|string|null} Properly formatted integer. | |
*/ | |
_fmt_num = (function (n) { | |
"use strict"; return n < 10 ? '0' + n : n; | |
}), | |
/** | |
* Properly quote/escape a string. | |
* | |
* @param {string} string regular, unquoted string to work on | |
* @returns {string} Properly quoted/escaped version of that string. | |
*/ | |
quote = (function (string) { | |
"use strict"; | |
// If the string contains no control characters, no quote characters, and no | |
// backslash characters, then we can safely slap some quotes around it. | |
// Otherwise we must also replace the offending characters with safe escape | |
// sequences. | |
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 + '"'; | |
}), | |
/** | |
* Stringify a native JavaScript value, at `key` in `holder`. | |
* | |
* @param {number|string} key where to pull from `holder` | |
* @param {Object} holder object to pull from at `key` | |
* @returns {string} Stringified JavaScript value. | |
*/ | |
str = (function str(key, holder) { | |
"use strict"; | |
// Produce a string from holder[key]. | |
var i, // The loop counter. | |
k, // The member key. | |
v, // The member value. | |
length, | |
mind = gap, | |
partial, | |
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); | |
} | |
// If we were called with a replacer function, then call the replacer to | |
// obtain a replacement value. | |
if (typeof rep === 'function') { | |
value = rep.call(holder, key, value); | |
} | |
// 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 (Object.prototype.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; | |
} | |
// If the replacer is an array, use it to select the members to be stringified. | |
if (rep && typeof rep === 'object') { | |
length = rep.length; | |
for (i = 0; i < length; i += 1) { | |
if (typeof rep[i] === 'string') { | |
k = rep[i]; | |
v = str(k, value); | |
if (v) { | |
partial.push(quote(k) + (gap ? ': ' : ':') + v); | |
} | |
} | |
} | |
} else { | |
// Otherwise, iterate through all of the keys in the object. | |
for (k in value) { | |
if (Object.prototype.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 ? | |
'{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' | |
: '{' + partial.join(',') + '}'; | |
gap = mind; | |
return v; | |
} | |
}), | |
JSON = context['JSON'] = context['JSON'] || { | |
/** | |
* Shim function to generate JSON from a given | |
* JavaScript value. | |
* | |
* @param {Object} value JavaScript value to encode as JSON. | |
* @param {Function} replacer Function to replace values, or array of strings to select keys. | |
* @returns {string} Serialized JSON from the given value at `value`. | |
*/ | |
stringify: (function (value, replacer, space) { | |
"use strict"; | |
// The stringify method takes a value and an optional replacer, and an optional | |
// space parameter, and returns a JSON text. The replacer can be a function | |
// that can replace values, or an array of strings that will select the keys. | |
// A default replacer method can be provided. Use of the space parameter can | |
// produce text that is more easily readable. | |
var i, gap = '', indent = ''; | |
// If the space parameter is a number, make an indent string containing that | |
// many spaces. | |
if (typeof space === 'number') { | |
for (i = 0; i < space; i += 1) { indent += ' '; } | |
// If the space parameter is a string, it will be used as the indent string. | |
} else if (typeof space === 'string') { indent = space; } | |
// If there is a replacer, it must be a function or an array. | |
// Otherwise, throw an error. | |
rep = replacer; | |
if (replacer && typeof replacer !== 'function' && | |
(typeof replacer !== 'object' || | |
typeof replacer.length !== 'number')) { | |
throw new Error('JSON.stringify'); | |
} | |
// Make a fake root object containing our value under the key of ''. | |
// Return the result of stringifying the value. | |
return str('', {'': value}); | |
} | |
), | |
/** | |
* Shim function to parse a JavaScript value | |
* from a JSON string. | |
* | |
* @param {string} text JSON string to decode. | |
* @param {Function} reviver Callback function to inflate special structures. | |
* @returns {Object} Hopefully, whatever object was encoded in JSON. | |
*/ | |
parse: (function (text, reviver) { | |
"use strict"; | |
// The parse method takes a text and an optional reviver function, and returns | |
// a JavaScript value if the text is a valid JSON text. | |
var j, walk = (function (holder, key) { | |
// The walk method is used to recursively walk the resulting structure so | |
// that modifications can be made. | |
var k, v, value = holder[key]; | |
if (value && typeof value === 'object') { | |
for (k in value) { | |
if (Object.prototype.hasOwnProperty.call(value, k)) { | |
v = walk(value, k); | |
if (v !== undefined) { | |
value[k] = v; | |
} else { | |
delete value[k]; | |
} | |
} | |
} | |
} return reviver.call(holder, key, value); | |
}); | |
// Parsing happens in four stages. In the first stage, we replace certain | |
// Unicode characters with escape sequences. JavaScript handles many characters | |
// incorrectly, either silently deleting them, or treating them as line endings. | |
text = String(text); | |
cx.lastIndex = 0; | |
if (cx.test(text)) { | |
text = text.replace(cx, function (a) { | |
return '\\u' + | |
('0000' + a.charCodeAt(0).toString(16)).slice(-4); | |
}); | |
} | |
// In the second stage, we run the text against regular expressions that look | |
// for non-JSON patterns. We are especially concerned with '()' and 'new' | |
// because they can cause invocation, and '=' because it can cause mutation. | |
// But just to be safe, we want to reject all unexpected forms. | |
// We split the second stage into 4 regexp operations in order to work around | |
// crippling inefficiencies in IE's and Safari's regexp engines. First we | |
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we | |
// replace all simple value tokens with ']' characters. Third, we delete all | |
// open brackets that follow a colon or comma or that begin the text. Finally, | |
// we look to see that the remaining characters are only whitespace or ']' or | |
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. | |
if (/^[\],:{}\s]*$/ | |
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') | |
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') | |
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { | |
// In the third stage we use the eval function to compile the text into a | |
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity | |
// in JavaScript: it can begin a block or an object literal. We wrap the text | |
// in parens to eliminate the ambiguity. | |
j = eval('(' + text + ')'); | |
// In the optional fourth stage, we recursively walk the new structure, passing | |
// each name/value pair to a reviver function for possible transformation. | |
return typeof reviver === 'function' ? walk({'': j}, '') : j; | |
} throw new SyntaxError('JSON.parse'); // If the text is not JSON parseable, then a SyntaxError is thrown. | |
} | |
) | |
}, | |
/** | |
* Converts a JavaScript `Date` to a JSON ISO-8601 | |
* representation, suitable for transmission to Keen. | |
* | |
* @returns {null|string} If the date provided is not finite, returns `null`. Otherwise, a valid ISO-8601 string. | |
*/ | |
_date_to_json = Date.prototype['toJSON'] = ((typeof Date.prototype['toJSON'] == 'function') ? | |
Date.prototype['toJSON'] : (function (key) { | |
"use strict"; | |
return isFinite(this.valueOf()) ? | |
this.getUTCFullYear() + '-' + | |
_fmt_num(this.getUTCMonth() + 1) + '-' + | |
_fmt_num(this.getUTCDate()) + 'T' + | |
_fmt_num(this.getUTCHours()) + ':' + | |
_fmt_num(this.getUTCMinutes()) + ':' + | |
_fmt_num(this.getUTCSeconds()) + 'Z' | |
: null; | |
})), | |
/** | |
* Calculates a JSON string, given the key | |
* it will be mounted at. Basically a proxy | |
* for basic types to `this.valueOf`. | |
* | |
* @returns {Object} Native value of the basetype. | |
*/ | |
_base_to_json = String.prototype['toJSON'] = Number.prototype['toJSON'] = Boolean.prototype['toJSON'] = (function (key) { | |
"use strict"; return this.valueOf(); // proxy to `valueOf` | |
}), | |
/** | |
* Gets the current client's timezone offset. | |
* | |
* @returns {Number} Timezone offset (in seconds). | |
*/ | |
getTimezoneOffset = context['getTimezoneOffset'] = (function (){ | |
"use strict"; return new Date().getTimezoneOffset() * -60; | |
}), | |
/** | |
* Determines whether Daylight Savings Time (DST) is active | |
* in the current environment and point in time. | |
* | |
* @returns {Boolean} True/False describing whether DST is currently active. | |
*/ | |
stdTimezoneOffset = Date.prototype['stdTimezoneOffset'] = (function() { | |
"use strict"; | |
var jan = new Date(this.getFullYear(), -1, 1), | |
jul = new Date(this.getFullYear(), 6, 1); | |
return Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset()); | |
}), | |
/** | |
* Determines whether Daylight Savings Time (DST) is active | |
* in the current environment and point in time. | |
* | |
* @returns {Boolean} True/False describing whether DST is currently active. | |
*/ | |
_date_dst = Date.prototype['dst'] = (function() { | |
"use strict"; return getTimezoneOffset() < stdTimezoneOffset(); | |
}), | |
/** | |
* Parses an ISO-8601 date into a vanilla javascript Date object. | |
* This is magic and is a modified version of this: https://github.com/csnover/js-iso8601/blob/master/iso8601.js | |
* | |
* @param date a string representation of a date in ISO-8601 format | |
* @returns {Date} JavaScript Date object from ISO-8601 timestamp provided at `date`. | |
*/ | |
parseDate = (function (date) { | |
"use strict"; | |
var numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ], timestamp, struct, minutesOffset = 0, i; | |
if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) { | |
for (i = 0, k; (k = numericKeys[i]); ++i) { | |
struct[k] = +struct[k] || 0; | |
} | |
struct[2] = (+struct[2] || 1) - 1; | |
struct[3] = +struct[3] || 1; | |
if (struct[8] !== 'Z' && struct[9] !== undefined) { | |
minutesOffset = struct[10] * 60 + struct[11]; | |
if (struct[9] === '+') { | |
minutesOffset = 0 - minutesOffset; | |
} | |
} | |
timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]); | |
} | |
else { | |
timestamp = Date.parse ? Date.parse(date) : NaN; | |
} | |
return new Date(timestamp); | |
}), | |
/** | |
* Takes in a map of keys & values; returns ampersand-delimited query string | |
* | |
* @param params map of key value pairs. | |
* @returns {string} Composed query string, encoded for use in a URL, from keys/values in `params` | |
*/ | |
makeQueryString = (function (params) { | |
"use strict"; | |
_.each(params, function(value, key) { | |
if(_.isUndefined(value)) { | |
delete params[key]; | |
} | |
}); | |
return _.map(params, function (value, key) { | |
if(!_.isString(value)) { | |
value = JSON.stringify(value); | |
} | |
value = encodeURIComponent(value); | |
return key + '=' + value; | |
}).join('&'); | |
}), | |
/** == Keen JS == **/ | |
Keen = context['Keen'] = /**@struct */ Keen || {}, | |
/*******************/ | |
/** | |
* Configure the Keen IO JS Library with a Project ID and API Keys. | |
* | |
* @param {Object} config Contains the project ID and optional keys. | |
* @param {string} config.projectId the Keen IO Project ID | |
* @param {string} [config.writeKey] the Keen IO Scoped Write Key | |
* @param {string} [config.readKey] the Keen IO Scoped Read Key | |
* @param {string} [config.keenUrl] the base url for the Keen IO API | |
* @returns {Keen.Client} An instance of Keen.Client that has been configured | |
*/ | |
_keen_configure = Keen['configure'] = (function (config) { | |
"use strict"; return (this.client = new Keen.Client(config)); | |
}), | |
/** | |
* Add an event to Keen IO. | |
* | |
* @param {string} eventCollection The name of the event collection | |
* @param {Object} event The actual event to send | |
* @param {Function} [success] Invoked on success | |
* @param {Function} [error] Invoked on failure | |
* @returns {null} | |
*/ | |
_add_event = Keen['addEvent'] = (function (eventCollection, event, success, error) { | |
"use strict"; | |
return (this.client ? this.client.uploadEvent(eventCollection, event, success, error) : _eq.push([eventCollection, event, success, error])); | |
}), | |
/** | |
* Add an event to Keen IO before navigating to an external page/submitting form | |
* | |
* @param {Element} htmlElement The html element being clicked/submitted | |
* @param {string} eventCollection The name of the event to be recorded | |
* @param {Object} event The event properties | |
* @param {Integer} [timeout=500] The amount of time to wait in milliseconds before timing out | |
* @param {Function} [timeoutCallback] Invoked on timeout | |
* @returns {Boolean} Returns false to prevent a default action from taking place | |
*/ | |
_track_external_link = Keen['trackExternalLink'] = (function(htmlElement, eventCollection, event, timeout, timeoutCallback){ | |
"use strict"; | |
var triggered = false, callback = (function(){}); | |
timeout = timeout || 500; | |
if( htmlElement.nodeName === "A"){ | |
callback = function(){ | |
if(!triggered){ | |
triggered = true; | |
window.location = htmlElement.href; | |
} | |
}; | |
} else if (htmlElement.nodeName === "FORM"){ | |
callback = function(){ | |
if(!triggered){ | |
triggered = true; | |
htmlElement.submit(); | |
} | |
}; | |
} | |
if(timeoutCallback){ | |
callback = function(){ | |
if(!triggered){ | |
triggered = true; | |
timeoutCallback(); | |
} | |
}; | |
} | |
Keen.addEvent(eventCollection, event, callback, callback); | |
setTimeout(function() { callback(); }, timeout); | |
return false; | |
}), | |
/** | |
* Sets the global properties to use. | |
* | |
* @param {Function} newGlobalProperties A function that returns an object of properties | |
* @returns null | |
*/ | |
_set_global_properties = Keen['setGlobalProperties'] = (function (newGlobalProperties) { | |
"use strict"; | |
if (this.client) { | |
if (newGlobalProperties && typeof(newGlobalProperties) == "function") { | |
this.client.globalProperties = newGlobalProperties; | |
} else { | |
throw new Error("Invalid value for global properties: " + newGlobalProperties); | |
} | |
} | |
}), | |
/* ==== KEEN CLIENT OBJECT ==== */ | |
_keen_client = Keen['Client'] = (function (config) { | |
"use strict"; | |
this.projectId = config.projectId; | |
this.writeKey = config.writeKey; | |
this.globalProperties = null; | |
if (context && context.location && context.location.protocol == 'https:') { | |
this.keenUrl = "https://api.keen.io"; // don't want to break existing SSL | |
} else { | |
this.keenUrl = "http://api.keen.io"; // otherwise default to HTTP | |
} | |
if(config !== undefined && config.keenUrl !== undefined){ | |
this.keenUrl = config.keenUrl; | |
} | |
}), | |
/** | |
* Uploads a single event to the Keen IO servers. | |
* | |
* @param {string} eventCollection The name of the event collection to use | |
* @param {Object} event The actual event properties to send | |
* @param {Function} [success] Invoked on success | |
* @param {Function} [error] Invoked on failure | |
* @returns {null} | |
*/ | |
_upload_event = Keen.Client.prototype['uploadEvent'] = (function (eventCollection, event, success, error) { | |
"use strict"; | |
var url = this.getKeenUrl("/events/" + eventCollection); | |
// handle global properties | |
var newEvent = {}; | |
if (this.globalProperties) { | |
newEvent = this.globalProperties(eventCollection); | |
} | |
// now add in the properties from the user-defined event | |
for (var property in event) { | |
if (event.hasOwnProperty(property)) { | |
newEvent[property] = event[property]; | |
} | |
} | |
var jsonBody = JSON.stringify(newEvent); | |
var base64Body = Keen.Base64.encode(jsonBody); | |
url = url + "?api_key=" + context.encodeURIComponent(this.writeKey); | |
url = url + "&data=" + context.encodeURIComponent(base64Body); | |
url = url + "&modified=" + context.encodeURIComponent(new Date().getTime()); | |
url = url + "&c=clv1"; | |
sendBeacon(url, null, success, error); | |
}), | |
/** | |
* Returns a full URL by appending the provided path to the root Keen IO URL. | |
* | |
* @param {string} path The path of the desired url | |
* @returns {string} A fully formed URL for use in an API call. | |
*/ | |
_get_keen_url = Keen.Client.prototype['getKeenUrl'] = (function (path) { | |
"use strict"; | |
return this.keenUrl + "/3.0/projects/" + this.projectId + path; | |
}), | |
/** | |
* Checks whether XMLHttpRequest is supported, facilitates fallback to JSONP. | |
* | |
* @returns {Boolean} true/false indicating XHR support and presence of `withCredentials` flag (XHR2). | |
*/ | |
supportsXhr = (function () { | |
"use strict"; | |
return (typeof XMLHttpRequest !== 'undefined') ? "withCredentials" in new XMLHttpRequest() : false; | |
}), | |
/** | |
* Send an event via an image beacon. | |
* | |
* Handles injection of an image beacon for recording | |
* events. | |
* | |
* @param {Object} Data to send for the event. | |
*/ | |
sendBeacon = context['sendBeacon'] = (function (url, apiKey, success, error) { | |
// add api_key if it's not there | |
if (apiKey && url.indexOf("api_key") < 0) { | |
var delimiterChar = url.indexOf("?") > 0 ? "&" : "?"; | |
url = url + delimiterChar + "api_key=" + apiKey; | |
} | |
var loaded = false, img = document.createElement("img"); | |
img.onload = function() { | |
loaded = true; | |
if ('naturalHeight' in this) { | |
if (this.naturalHeight + this.naturalWidth === 0) { | |
this.onerror(); return; | |
} | |
} else if (this.width + this.height === 0) { | |
this.onerror(); return; | |
} | |
if (success) { success({created: true}); } | |
}; | |
img.onerror = function() { | |
loaded = true; | |
if (error) { | |
error(); | |
} | |
}; | |
img.src = url; | |
}), | |
/** | |
* | |
* Base64 encode / decode | |
* https://gist.github.com/sgammon/5562296 | |
* | |
**/ | |
Base64 = context['Base64'] = Keen['Base64'] = context['Base64'] || { | |
map: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", | |
encode: function (n) { | |
"use strict"; | |
var o = "", i = 0, m = this.map, i1, i2, i3, e1, e2, e3, e4; | |
n = this.utf8.encode(n); | |
while (i < n.length) { | |
i1 = n.charCodeAt(i++); i2 = n.charCodeAt(i++); i3 = n.charCodeAt(i++); | |
e1 = (i1 >> 2); e2 = (((i1 & 3) << 4) | (i2 >> 4)); e3 = (isNaN(i2) ? 64 : ((i2 & 15) << 2) | (i3 >> 6)); | |
e4 = (isNaN(i2) || isNaN(i3)) ? 64 : i3 & 63; | |
o = o + m.charAt(e1) + m.charAt(e2) + m.charAt(e3) + m.charAt(e4); | |
} return o; | |
}, | |
decode: function (n) { | |
"use strict"; | |
var o = "", i = 0, m = this.map, cc = String.fromCharCode, e1, e2, e3, e4, c1, c2, c3; | |
n = n.replace(/[^A-Za-z0-9\+\/\=]/g, ""); | |
while (i < n.length) { | |
e1 = m.indexOf(n.charAt(i++)); e2 = m.indexOf(n.charAt(i++)); | |
e3 = m.indexOf(n.charAt(i++)); e4 = m.indexOf(n.charAt(i++)); | |
c1 = (e1 << 2) | (e2 >> 4); c2 = ((e2 & 15) << 4) | (e3 >> 2); | |
c3 = ((e3 & 3) << 6) | e4; | |
o = o + (cc(c1) + ((e3 != 64) ? cc(c2) : "")) + (((e4 != 64) ? cc(c3) : "")); | |
} return this.utf8.decode(o); | |
}, | |
utf8: { | |
encode: function (n) { | |
"use strict"; | |
var o = "", i = 0, cc = String.fromCharCode, c; | |
while (i < n.length) { | |
c = n.charCodeAt(i++); o = o + ((c < 128) ? cc(c) : ((c > 127) && (c < 2048)) ? | |
(cc((c >> 6) | 192) + cc((c & 63) | 128)) : (cc((c >> 12) | 224) + cc(((c >> 6) & 63) | 128) + cc((c & 63) | 128))); | |
} return o; | |
}, | |
decode: function (n) { | |
"use strict"; | |
var o = "", i = 0, cc = String.fromCharCode, c2, c; | |
while (i < n.length) { | |
c = n.charCodeAt(i); | |
o = o + ((c < 128) ? [cc(c), i++][0] : ((c > 191) && (c < 224)) ? | |
[cc(((c & 31) << 6) | ((c2 = n.charCodeAt(i + 1)) & 63)), (i += 2)][0] : | |
[cc(((c & 15) << 12) | (((c2 = n.charCodeAt(i + 1)) & 63) << 6) | ((c3 = n.charCodeAt(i + 2)) & 63)), (i += 3)][0]); | |
} return o; | |
} | |
} | |
}; | |
// handle any queued commands | |
(function (_cf, _gp, _eq) { | |
"use strict"; | |
var event, eventCollection, success, error, i; | |
if (debug) { log('Keen ready.', Keen); } | |
if (_cf) { Keen.configure(_cf); } | |
if (_gp) { Keen.setGlobalProperties(_gp); } | |
if (_eq && _eq.length > 0) { for (i = 0; i < _eq.length; i++) { | |
eventCollection = _eq[i].shift(); | |
event = _eq[i].shift(); | |
success = _eq[i].shift(); | |
error = _eq[i].shift(); | |
Keen.addEvent(eventCollection, event, success, error); | |
} | |
} | |
})(_cf, _gp, _eq); | |
})(window || {}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Keen JS - minimal SDK, version 2.3.2 */ | |
(function(m){var r=m.Keen||{},w=r._eq||[],x=r._cf||{},r=r._gp||{},t=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,u=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,q,p,y={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},s=function(f){return 10>f?"0"+f:f},v=function(f){u.lastIndex=0;return u.test(f)?'"'+f.replace(u,function(a){var d=y[a]; | |
return"string"===typeof d?d:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+f+'"'},z=function a(d,c){var b,e,g,n,f=p,h,l=c[d];l&&"object"===typeof l&&"function"===typeof l.toJSON&&(l=l.toJSON(d));"function"===typeof q&&(l=q.call(c,d,l));switch(typeof l){case "string":return v(l);case "number":return isFinite(l)?String(l):"null";case "boolean":case "null":return String(l);case "object":if(!l)return"null";p+=void 0;h=[];if("[object Array]"===Object.prototype.toString.apply(l)){n=l.length; | |
for(b=0;b<n;b+=1)h[b]=a(b,l)||"null";g=0===h.length?"[]":p?"[\n"+p+h.join(",\n"+p)+"\n"+f+"]":"["+h.join(",")+"]";p=f;return g}if(q&&"object"===typeof q)for(n=q.length,b=0;b<n;b+=1)"string"===typeof q[b]&&(e=q[b],(g=a(e,l))&&h.push(v(e)+(p?": ":":")+g));else for(e in l)Object.prototype.hasOwnProperty.call(l,e)&&(g=a(e,l))&&h.push(v(e)+(p?": ":":")+g);g=0===h.length?"{}":p?"{\n"+p+h.join(",\n"+p)+"\n"+f+"}":"{"+h.join(",")+"}";p=f;return g}},A=m.JSON=m.JSON||{stringify:function(a,d,c){var b;if("number"=== | |
typeof c)for(b=0;b<c;b+=1);if((q=d)&&"function"!==typeof d&&("object"!==typeof d||"number"!==typeof d.length))throw Error("JSON.stringify");return z("",{"":a})},parse:function(a,d){var c,b=function(a,c){var n,f,h=a[c];if(h&&"object"===typeof h)for(n in h)Object.prototype.hasOwnProperty.call(h,n)&&(f=b(h,n),void 0!==f?h[n]=f:delete h[n]);return d.call(a,c,h)};a=String(a);t.lastIndex=0;t.test(a)&&(a=a.replace(t,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, | |
"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return c=eval("("+a+")"),"function"===typeof d?b({"":c},""):c;throw new SyntaxError("JSON.parse");}};Date.prototype.toJSON="function"==typeof Date.prototype.toJSON?Date.prototype.toJSON:function(a){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+s(this.getUTCMonth()+1)+"-"+s(this.getUTCDate())+"T"+s(this.getUTCHours())+":"+s(this.getUTCMinutes())+":"+s(this.getUTCSeconds())+ | |
"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()};var B=m.getTimezoneOffset=function(){return-60*(new Date).getTimezoneOffset()},C=Date.prototype.stdTimezoneOffset=function(){var a=new Date(this.getFullYear(),-1,1),d=new Date(this.getFullYear(),6,1);return Math.max(a.getTimezoneOffset(),d.getTimezoneOffset())};Date.prototype.dst=function(){return B()<C()};var f=m.Keen=f||{};f.configure=function(a){return this.client=new f.Client(a)}; | |
f.addEvent=function(a,d,c,b){return this.client?this.client.uploadEvent(a,d,c,b):w.push([a,d,c,b])};f.trackExternalLink=function(a,d,c,b,e){var g=!1,n=function(){};b=b||500;"A"===a.nodeName?n=function(){g||(g=!0,window.location=a.href)}:"FORM"===a.nodeName&&(n=function(){g||(g=!0,a.submit())});e&&(n=function(){g||(g=!0,e())});f.addEvent(d,c,n,n);setTimeout(function(){n()},b);return!1};f.setGlobalProperties=function(a){if(this.client)if(a&&"function"==typeof a)this.client.globalProperties=a;else throw Error("Invalid value for global properties: "+ | |
a);};f.Client=function(a){this.projectId=a.projectId;this.writeKey=a.writeKey;this.globalProperties=null;this.keenUrl=m&&m.location&&"https:"==m.location.protocol?"https://api.keen.io":"http://api.keen.io";void 0!==a&&void 0!==a.keenUrl&&(this.keenUrl=a.keenUrl)};f.Client.prototype.uploadEvent=function(a,d,c,b){var e=this.getKeenUrl("/events/"+a),g={};this.globalProperties&&(g=this.globalProperties(a));for(var n in d)d.hasOwnProperty(n)&&(g[n]=d[n]);a=A.stringify(g);a=f.Base64.encode(a);e=e+"?api_key="+ | |
m.encodeURIComponent(this.writeKey);e=e+"&data="+m.encodeURIComponent(a);e=e+"&modified="+m.encodeURIComponent((new Date).getTime());D(e+"&c=clv1",null,c,b)};f.Client.prototype.getKeenUrl=function(a){return this.keenUrl+"/3.0/projects/"+this.projectId+a};var D=m.sendBeacon=function(a,d,c,b){if(d&&0>a.indexOf("api_key")){var e=0<a.indexOf("?")?"&":"?";a=a+e+"api_key="+d}d=document.createElement("img");d.onload=function(){if("naturalHeight"in this){if(0===this.naturalHeight+this.naturalWidth){this.onerror(); | |
return}}else if(0===this.width+this.height){this.onerror();return}c&&c({created:!0})};d.onerror=function(){b&&b()};d.src=a};m.Base64=f.Base64=m.Base64||{map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(a){var d="",c=0,b=this.map,e,g,f,m,h;for(a=this.utf8.encode(a);c<a.length;)e=a.charCodeAt(c++),g=a.charCodeAt(c++),f=a.charCodeAt(c++),m=e>>2,e=(e&3)<<4|g>>4,h=isNaN(g)?64:(g&15)<<2|f>>6,g=isNaN(g)||isNaN(f)?64:f&63,d=d+b.charAt(m)+b.charAt(e)+b.charAt(h)+b.charAt(g); | |
return d},decode:function(a){var d="",c=0,b=this.map,e=String.fromCharCode,g,f,m,h,l;for(a=a.replace(/[^A-Za-z0-9\+\/\=]/g,"");c<a.length;)g=b.indexOf(a.charAt(c++)),f=b.indexOf(a.charAt(c++)),m=b.indexOf(a.charAt(c++)),h=b.indexOf(a.charAt(c++)),g=g<<2|f>>4,f=(f&15)<<4|m>>2,l=(m&3)<<6|h,d=d+(e(g)+(64!=m?e(f):""))+(64!=h?e(l):"");return this.utf8.decode(d)},utf8:{encode:function(a){for(var d="",c=0,b=String.fromCharCode,e;c<a.length;)e=a.charCodeAt(c++),d+=128>e?b(e):127<e&&2048>e?b(e>>6|192)+b(e& | |
63|128):b(e>>12|224)+b(e>>6&63|128)+b(e&63|128);return d},decode:function(a){for(var d="",c=0,b=String.fromCharCode,e;c<a.length;)e=a.charCodeAt(c),d+=128>e?[b(e),c++][0]:191<e&&224>e?[b((e&31)<<6|a.charCodeAt(c+1)&63),c+=2][0]:[b((e&15)<<12|(a.charCodeAt(c+1)&63)<<6|(c3=a.charCodeAt(c+2))&63),c+=3][0];return d}}};(function(a,d,c){var b,e,g;a&&f.configure(a);d&&f.setGlobalProperties(d);if(c&&0<c.length)for(g=0;g<c.length;g++)d=c[g].shift(),a=c[g].shift(),b=c[g].shift(),e=c[g].shift(),f.addEvent(d, | |
a,b,e)})(x,r,w)})(window||{}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment