Skip to content

Instantly share code, notes, and snippets.

@sgammon
Last active August 29, 2015 13:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sgammon/8725478 to your computer and use it in GitHub Desktop.
Save sgammon/8725478 to your computer and use it in GitHub Desktop.
Minimal rewrite of Keen IO's JavaScript library :) version 2.3.1
/***
*
*
* /$$ /$$ /$$$$$ /$$$$$$
* | $$ /$$/ |__ $$ /$$__ $$
* | $$ /$$/ /$$$$$$ /$$$$$$ /$$$$$$$ | $$| $$ \__/
* | $$$$$/ /$$__ $$ /$$__ $$| $$__ $$ | $$| $$$$$$
* | $$ $$ | $$$$$$$$| $$$$$$$$| $$ \ $$ /$$ | $$ \____ $$
* | $$\ $$ | $$_____/| $$_____/| $$ | $$ | $$ | $$ /$$ \ $$
* | $$ \ $$| $$$$$$$| $$$$$$$| $$ | $$ | $$$$$$/| $$$$$$/
* |__/ \__/ \_______/ \_______/|__/ |__/ \______/ \______/
*
* made with love <3 in san francisco
* ============ v 2.3.1 ===========
*/
(function (context) {
var debug = /** @const */ false,
_eq = context['_eq'] || [], _cf = context['_cf'] || [], _gp = context['_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;
if (debug) { log('Keen ready.', Keen); }
if (_cf) { Keen.configure(_cf); }
if (_gp) { Keen.setGlobalProperties(Keen._gp); }
if (_eq && _eq.length > 0) { for (i = 0; i < Keen._eq.length; i++) {
eventCollection = Keen._eq[i].shift();
event = Keen._eq[i].shift();
success = Keen._eq[i].shift();
error = Keen._eq[i].shift();
Keen.addEvent(eventCollection, event, success, error);
}
}
})(Keen._cf, Keen._gp, Keen._eq);
})(window || {});
/* Keen JS - minimal SDK, version 2.3.1 */
(function(p){var w=p._eq||[],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,r,q,x={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},s=function(b){return 10>b?"0"+b:b},v=function(b){u.lastIndex=0;return u.test(b)?'"'+b.replace(u,function(a){var f=x[a];return"string"===typeof f?f:"\\u"+
("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+b+'"'},y=function a(f,d){var c,e,h,b,n=q,g,l=d[f];l&&"object"===typeof l&&"function"===typeof l.toJSON&&(l=l.toJSON(f));"function"===typeof r&&(l=r.call(d,f,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";q+=void 0;g=[];if("[object Array]"===Object.prototype.toString.apply(l)){b=l.length;for(c=0;c<b;c+=1)g[c]=a(c,l)||
"null";h=0===g.length?"[]":q?"[\n"+q+g.join(",\n"+q)+"\n"+n+"]":"["+g.join(",")+"]";q=n;return h}if(r&&"object"===typeof r)for(b=r.length,c=0;c<b;c+=1)"string"===typeof r[c]&&(e=r[c],(h=a(e,l))&&g.push(v(e)+(q?": ":":")+h));else for(e in l)Object.prototype.hasOwnProperty.call(l,e)&&(h=a(e,l))&&g.push(v(e)+(q?": ":":")+h);h=0===g.length?"{}":q?"{\n"+q+g.join(",\n"+q)+"\n"+n+"}":"{"+g.join(",")+"}";q=n;return h}},z=p.JSON=p.JSON||{stringify:function(a,f,d){var c;if("number"===typeof d)for(c=0;c<d;c+=
1);if((r=f)&&"function"!==typeof f&&("object"!==typeof f||"number"!==typeof f.length))throw Error("JSON.stringify");return y("",{"":a})},parse:function(a,f){var d,c=function(a,d){var b,n,g=a[d];if(g&&"object"===typeof g)for(b in g)Object.prototype.hasOwnProperty.call(g,b)&&(n=c(g,b),void 0!==n?g[b]=n:delete g[b]);return f.call(a,d,g)};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 d=eval("("+a+")"),"function"===typeof f?c({"":d},""):d;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 A=p.getTimezoneOffset=function(){return-60*(new Date).getTimezoneOffset()},B=Date.prototype.stdTimezoneOffset=function(){var a=new Date(this.getFullYear(),-1,1),f=new Date(this.getFullYear(),6,1);return Math.max(a.getTimezoneOffset(),f.getTimezoneOffset())};Date.prototype.dst=function(){return A()<B()};var b=p.Keen=b||{};b.configure=function(a){return this.client=new b.Client(a)};
b.addEvent=function(a,f,d,c){return this.client?this.client.uploadEvent(a,f,d,c):w.push([a,f,d,c])};b.trackExternalLink=function(a,f,d,c,e){var h=!1,m=function(){};c=c||500;"A"===a.nodeName?m=function(){h||(h=!0,window.location=a.href)}:"FORM"===a.nodeName&&(m=function(){h||(h=!0,a.submit())});e&&(m=function(){h||(h=!0,e())});b.addEvent(f,d,m,m);setTimeout(function(){m()},c);return!1};b.setGlobalProperties=function(a){if(this.client)if(a&&"function"==typeof a)this.client.globalProperties=a;else throw Error("Invalid value for global properties: "+
a);};b.Client=function(a){this.projectId=a.projectId;this.writeKey=a.writeKey;this.globalProperties=null;this.keenUrl=p&&p.location&&"https:"==p.location.protocol?"https://api.keen.io":"http://api.keen.io";void 0!==a&&void 0!==a.keenUrl&&(this.keenUrl=a.keenUrl)};b.Client.prototype.uploadEvent=function(a,f,d,c){var e=this.getKeenUrl("/events/"+a),h={};this.globalProperties&&(h=this.globalProperties(a));for(var m in f)f.hasOwnProperty(m)&&(h[m]=f[m]);a=z.stringify(h);a=b.Base64.encode(a);e=e+"?api_key="+
p.encodeURIComponent(this.writeKey);e=e+"&data="+p.encodeURIComponent(a);e=e+"&modified="+p.encodeURIComponent((new Date).getTime());C(e+"&c=clv1",null,d,c)};b.Client.prototype.getKeenUrl=function(a){return this.keenUrl+"/3.0/projects/"+this.projectId+a};var C=p.sendBeacon=function(a,f,d,c){if(f&&0>a.indexOf("api_key")){var e=0<a.indexOf("?")?"&":"?";a=a+e+"api_key="+f}f=document.createElement("img");f.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}d&&d({created:!0})};f.onerror=function(){c&&c()};f.src=a};p.Base64=b.Base64=p.Base64||{map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(a){var f="",d=0,c=this.map,e,b,m,n,g;for(a=this.utf8.encode(a);d<a.length;)e=a.charCodeAt(d++),b=a.charCodeAt(d++),m=a.charCodeAt(d++),n=e>>2,e=(e&3)<<4|b>>4,g=isNaN(b)?64:(b&15)<<2|m>>6,b=isNaN(b)||isNaN(m)?64:m&63,f=f+c.charAt(n)+c.charAt(e)+c.charAt(g)+c.charAt(b);
return f},decode:function(a){var f="",d=0,c=this.map,b=String.fromCharCode,h,m,n,g,l;for(a=a.replace(/[^A-Za-z0-9\+\/\=]/g,"");d<a.length;)h=c.indexOf(a.charAt(d++)),m=c.indexOf(a.charAt(d++)),n=c.indexOf(a.charAt(d++)),g=c.indexOf(a.charAt(d++)),h=h<<2|m>>4,m=(m&15)<<4|n>>2,l=(n&3)<<6|g,f=f+(b(h)+(64!=n?b(m):""))+(64!=g?b(l):"");return this.utf8.decode(f)},utf8:{encode:function(a){for(var b="",d=0,c=String.fromCharCode,e;d<a.length;)e=a.charCodeAt(d++),b+=128>e?c(e):127<e&&2048>e?c(e>>6|192)+c(e&
63|128):c(e>>12|224)+c(e>>6&63|128)+c(e&63|128);return b},decode:function(a){for(var b="",d=0,c=String.fromCharCode,e;d<a.length;)e=a.charCodeAt(d),b+=128>e?[c(e),d++][0]:191<e&&224>e?[c((e&31)<<6|a.charCodeAt(d+1)&63),d+=2][0]:[c((e&15)<<12|(a.charCodeAt(d+1)&63)<<6|(c3=a.charCodeAt(d+2))&63),d+=3][0];return b}}};(function(a,f,d){var c;a&&b.configure(a);f&&b.setGlobalProperties(b._gp);if(d&&0<d.length)for(i=0;i<b._eq.length;i++)f=b._eq[i].shift(),a=b._eq[i].shift(),d=b._eq[i].shift(),c=b._eq[i].shift(),
b.addEvent(f,a,d,c)})(b._cf,b._gp,b._eq)})(window||{});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment