ashish-sdk
Version:
ThoughtSpot Embed SDK
1,436 lines (1,356 loc) • 385 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.tsembed = {}));
}(this, (function (exports) { 'use strict';
/**
* Copyright (c) 2022
*
* Common utility functions for ThoughtSpot Visual Embed SDK
*
* @summary Utils
* @author Ayon Ghosh <ayon.ghosh@thoughtspot.com>
*/
/**
* Construct a runtime filters query string from the given filters.
* Refer to the following docs for more details on runtime filter syntax:
* https://cloud-docs.thoughtspot.com/admin/ts-cloud/apply-runtime-filter.html
* https://cloud-docs.thoughtspot.com/admin/ts-cloud/runtime-filter-operators.html
* @param runtimeFilters
*/
const getFilterQuery = (runtimeFilters) => {
if (runtimeFilters && runtimeFilters.length) {
const filters = runtimeFilters.map((filter, valueIndex) => {
const index = valueIndex + 1;
const filterExpr = [];
filterExpr.push(`col${index}=${filter.columnName}`);
filterExpr.push(`op${index}=${filter.operator}`);
filterExpr.push(filter.values.map((value) => `val${index}=${value}`).join('&'));
return filterExpr.join('&');
});
return `${filters.join('&')}`;
}
return null;
};
/**
* Convert a value to a string representation to be sent as a query
* parameter to the ThoughtSpot app.
* @param value Any parameter value
*/
const serializeParam = (value) => {
// do not serialize primitive types
if (typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean') {
return value;
}
return JSON.stringify(value);
};
/**
* Convert a value to a string:
* in case of an array, we convert it to CSV.
* in case of any other type, we directly return the value.
* @param value
*/
const paramToString = (value) => Array.isArray(value) ? value.join(',') : value;
/**
* Return a query param string composed from the given params object
* @param queryParams
*/
const getQueryParamString = (queryParams, shouldSerializeParamValues = false) => {
const qp = [];
const params = Object.keys(queryParams);
params.forEach((key) => {
const val = queryParams[key];
if (val !== undefined) {
const serializedValue = shouldSerializeParamValues
? serializeParam(val)
: paramToString(val);
qp.push(`${key}=${serializedValue}`);
}
});
if (qp.length) {
return qp.join('&');
}
return null;
};
/**
* Get a string representation of a dimension value in CSS
* If numeric, it is considered in pixels.
* @param value
*/
const getCssDimension = (value) => {
if (typeof value === 'number') {
return `${value}px`;
}
return value;
};
/**
* Append a string to a URL's hash fragment
* @param url A URL
* @param stringToAppend The string to append to the URL hash
*/
const appendToUrlHash = (url, stringToAppend) => {
let outputUrl = url;
const encStringToAppend = encodeURIComponent(stringToAppend);
if (url.indexOf('#') >= 0) {
outputUrl = `${outputUrl}${encStringToAppend}`;
}
else {
outputUrl = `${outputUrl}#${encStringToAppend}`;
}
return outputUrl;
};
const getEncodedQueryParamsString = (queryString) => {
if (!queryString) {
return queryString;
}
return btoa(queryString)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
};
const getOffsetTop = (element) => {
const rect = element.getBoundingClientRect();
return rect.top + window.scrollY;
};
/**
* Copyright (c) 2022
*
* TypeScript type definitions for ThoughtSpot Visual Embed SDK
*
* @summary Type definitions for Embed SDK
* @author Ayon Ghosh <ayon.ghosh@thoughtspot.com>
*/
(function (AuthType) {
/**
* No authentication. Use this only for testing purposes.
*/
AuthType["None"] = "None";
/**
* SSO using SAML
*/
AuthType["SSO"] = "SSO_SAML";
/**
* SSO using OIDC
*/
AuthType["OIDC"] = "SSO_OIDC";
/**
* Trusted authentication server
*/
AuthType["AuthServer"] = "AuthServer";
/**
* Use the ThoughtSpot login API to authenticate to the cluster directly.
*
* Warning: This feature is primarily intended for developer testing. It is
* strongly advised not to use this authentication method in production.
*/
AuthType["Basic"] = "Basic";
})(exports.AuthType || (exports.AuthType = {}));
(function (RuntimeFilterOp) {
/**
* Equals
*/
RuntimeFilterOp["EQ"] = "EQ";
/**
* Does not equal
*/
RuntimeFilterOp["NE"] = "NE";
/**
* Less than
*/
RuntimeFilterOp["LT"] = "LT";
/**
* Less than or equal to
*/
RuntimeFilterOp["LE"] = "LE";
/**
* Greater than
*/
RuntimeFilterOp["GT"] = "GT";
/**
* Greater than or equal to
*/
RuntimeFilterOp["GE"] = "GE";
/**
* Contains
*/
RuntimeFilterOp["CONTAINS"] = "CONTAINS";
/**
* Begins with
*/
RuntimeFilterOp["BEGINS_WITH"] = "BEGINS_WITH";
/**
* Ends with
*/
RuntimeFilterOp["ENDS_WITH"] = "ENDS_WITH";
/**
* Between, inclusive of higher value
*/
RuntimeFilterOp["BW_INC_MAX"] = "BW_INC_MAX";
/**
* Between, inclusive of lower value
*/
RuntimeFilterOp["BW_INC_MIN"] = "BW_INC_MIN";
/**
* Between, inclusive of both higher and lower value
*/
RuntimeFilterOp["BW_INC"] = "BW_INC";
/**
* Between, non-inclusive
*/
RuntimeFilterOp["BW"] = "BW";
/**
* Is included in this list of values
*/
RuntimeFilterOp["IN"] = "IN";
})(exports.RuntimeFilterOp || (exports.RuntimeFilterOp = {}));
(function (EmbedEvent) {
/**
* Rendering has initialized.
* @return timestamp - The timestamp when the event was generated.
*/
EmbedEvent["Init"] = "init";
/**
* Authentication has either succeeded or failed.
* @return isLoggedIn - A Boolean specifying whether authentication was successful.
*/
EmbedEvent["AuthInit"] = "authInit";
/**
* The embed object container has loaded.
* @return timestamp - The timestamp when the event was generated.
*/
EmbedEvent["Load"] = "load";
/**
* Data pertaining to answer or Liveboard is received
* @return data - The answer or Liveboard data
*/
EmbedEvent["Data"] = "data";
/**
* Search/answer/Liveboard filters have been applied/updated
* @hidden
*/
EmbedEvent["FiltersChanged"] = "filtersChanged";
/**
* Search query has been updated
*/
EmbedEvent["QueryChanged"] = "queryChanged";
/**
* A drill down operation has been performed.
* @return additionalFilters - Any additional filters applied
* @return drillDownColumns - The columns on which drill down was performed
* @return nonFilteredColumns - The columns that were not filtered
*/
EmbedEvent["Drilldown"] = "drillDown";
/**
* One or more data sources have been selected.
* @return dataSourceIds - the list of data sources
*/
EmbedEvent["DataSourceSelected"] = "dataSourceSelected";
/**
* One or more data columns have been selected.
* @return columnIds - the list of columns
* @version SDK: 1.9.0 | ThoughtSpot: 8.2.0.cl
*/
EmbedEvent["AddRemoveColumns"] = "addRemoveColumns";
/**
* A custom action has been triggered
* @return actionId - The id of the custom action
* @return data - The answer or Liveboard data
*/
EmbedEvent["CustomAction"] = "customAction";
/**
* A click has been triggered on table/chart
* @return ContextMenuInputPoints - data point that is double clicked
* @version 1.10.0
*/
EmbedEvent["VizPointClick"] = "vizPointClick";
/**
* A double click has been triggered on table/chart
* @return ContextMenuInputPoints - data point that is double clicked
* @version 1.5.0 or later
*/
EmbedEvent["VizPointDoubleClick"] = "vizPointDoubleClick";
/**
* Event troggered when rendering a chart, call the supplied
* callback with the config overrides.
* @version 1.10.0
*/
EmbedEvent["GetVizConfigOverrides"] = "getVizConfigOverrides";
/**
* An error has occurred.
* @return error - An error object or message
*/
EmbedEvent["Error"] = "Error";
/**
* The embedded object has sent an alert
* @return alert - An alert object
*/
EmbedEvent["Alert"] = "alert";
/**
* The ThoughtSpot auth session has expired.
*/
EmbedEvent["AuthExpire"] = "ThoughtspotAuthExpired";
/**
* The height of the embedded Liveboard or visualization has been computed.
* @return data - The height of the embedded Liveboard or visualization
* @hidden
*/
EmbedEvent["EmbedHeight"] = "EMBED_HEIGHT";
/**
* The center of visible iframe viewport is calculated.
* @return data - The center of the visible Iframe viewport.
* @hidden
*/
EmbedEvent["EmbedIframeCenter"] = "EmbedIframeCenter";
/**
* Detects the route change.
*/
EmbedEvent["RouteChange"] = "ROUTE_CHANGE";
/**
* The v1 event type for Data
* @hidden
*/
EmbedEvent["V1Data"] = "exportVizDataToParent";
/**
* Emitted when the embed does not have cookie access. This
* happens on Safari where third-party cookies are blocked by default.
*
* @version 1.1.0 or later
*/
EmbedEvent["NoCookieAccess"] = "noCookieAccess";
/**
* Emitted when SAML is complete
* @private
* @hidden
*/
EmbedEvent["SAMLComplete"] = "samlComplete";
/**
* Emitted when any modal is opened in the app
* @version 1.6.0 or later
*/
EmbedEvent["DialogOpen"] = "dialog-open";
/**
* Emitted when any modal is closed in the app
* @version 1.6.0 or later
*/
EmbedEvent["DialogClose"] = "dialog-close";
})(exports.EmbedEvent || (exports.EmbedEvent = {}));
(function (HostEvent) {
/**
* Trigger a search
* @param dataSourceIds - The list of data source GUIDs
* @param searchQuery - The search query
*/
HostEvent["Search"] = "search";
/**
* Trigger a drill on certain points by certain column
* @param points - an object containing selectedPoints/clickedPoints
* eg. { selectedPoints: []}
* @param columnGuid - a string guid of the column to drill by. This is optional,
* if not provided it will auto drill by the configured column. \
* @version 1.5.0 or later
*/
HostEvent["DrillDown"] = "triggerDrillDown";
/**
* Apply filters
* @hidden
*/
HostEvent["Filter"] = "filter";
/**
* Reload the answer or visualization
* @hidden
*/
HostEvent["Reload"] = "reload";
/**
* Set the visible visualizations on a Liveboard.
* @param - an array of ids of visualizations to show, the ids not passed
* will be hidden.
* @version 1.6.0 or later
*/
HostEvent["SetVisibleVizs"] = "SetPinboardVisibleVizs";
/**
* Update the runtime filters. The runtime filters passed here are extended
* on to the existing runtime filters if they exist.
* @param - {@link RuntimeFilter}[] an array of {@link RuntimeFilter} Types.
* @version 1.8.0 or later
*/
HostEvent["UpdateRuntimeFilters"] = "UpdateRuntimeFilters";
/**
* Highlight the point in the chart defined by the InputPoint passed.
* @verion 1.10.0
*/
HostEvent["HighlightPoint"] = "HighlightPoint";
})(exports.HostEvent || (exports.HostEvent = {}));
(function (DataSourceVisualMode) {
/**
* Data source panel is hidden.
*/
DataSourceVisualMode["Hidden"] = "hide";
/**
* Data source panel is collapsed, but the user can manually expand it.
*/
DataSourceVisualMode["Collapsed"] = "collapse";
/**
* Data source panel is expanded, but the user can manually collapse it.
*/
DataSourceVisualMode["Expanded"] = "expand";
})(exports.DataSourceVisualMode || (exports.DataSourceVisualMode = {}));
/**
* The query params passed down to the embedded ThoughtSpot app
* containing configuration and/or visual information.
*/
// eslint-disable-next-line no-shadow
var Param;
(function (Param) {
Param["DataSources"] = "dataSources";
Param["DataSourceMode"] = "dataSourceMode";
Param["DisableActions"] = "disableAction";
Param["DisableActionReason"] = "disableHint";
Param["ForceTable"] = "forceTable";
Param["preventLiveboardFilterRemoval"] = "preventPinboardFilterRemoval";
Param["SearchQuery"] = "searchQuery";
Param["HideActions"] = "hideAction";
Param["HideObjects"] = "hideObjects";
Param["HostAppUrl"] = "hostAppUrl";
Param["EnableVizTransformations"] = "enableVizTransform";
Param["EnableSearchAssist"] = "enableSearchAssist";
Param["HideResult"] = "hideResult";
Param["UseLastSelectedDataSource"] = "useLastSelectedSources";
Param["Tag"] = "tag";
Param["searchTokenString"] = "searchTokenString";
Param["executeSearch"] = "executeSearch";
Param["fullHeight"] = "isFullHeightPinboard";
Param["livedBoardEmbed"] = "isLiveboardEmbed";
Param["searchEmbed"] = "isSearchEmbed";
Param["Version"] = "sdkVersion";
Param["ViewPortHeight"] = "viewPortHeight";
Param["ViewPortWidth"] = "viewPortWidth";
Param["VisibleActions"] = "visibleAction";
Param["CustomCSSUrl"] = "customCssUrl";
})(Param || (Param = {}));
(function (Action) {
Action["Save"] = "save";
Action["Update"] = "update";
Action["SaveUntitled"] = "saveUntitled";
Action["SaveAsView"] = "saveAsView";
Action["MakeACopy"] = "makeACopy";
Action["EditACopy"] = "editACopy";
Action["CopyLink"] = "embedDocument";
Action["ResetLayout"] = "resetLayout";
Action["Schedule"] = "subscription";
Action["SchedulesList"] = "schedule-list";
Action["Share"] = "share";
Action["AddFilter"] = "addFilter";
Action["ConfigureFilter"] = "configureFilter";
Action["AddFormula"] = "addFormula";
Action["SearchOnTop"] = "searchOnTop";
Action["SpotIQAnalyze"] = "spotIQAnalyze";
Action["ExplainInsight"] = "explainInsight";
Action["SpotIQFollow"] = "spotIQFollow";
Action["ShareViz"] = "shareViz";
Action["ReplaySearch"] = "replaySearch";
Action["ShowUnderlyingData"] = "showUnderlyingData";
Action["Download"] = "download";
Action["DownloadAsPdf"] = "downloadAsPdf";
Action["DownloadAsCsv"] = "downloadAsCSV";
Action["DownloadAsXlsx"] = "downloadAsXLSX";
Action["DownloadTrace"] = "downloadTrace";
Action["ExportTML"] = "exportTSL";
Action["ImportTML"] = "importTSL";
Action["UpdateTML"] = "updateTSL";
Action["EditTML"] = "editTSL";
Action["Present"] = "present";
Action["ToggleSize"] = "toggleSize";
Action["Edit"] = "edit";
Action["EditTitle"] = "editTitle";
Action["Remove"] = "delete";
Action["Ungroup"] = "ungroup";
Action["Describe"] = "describe";
Action["Relate"] = "relate";
Action["CustomizeHeadlines"] = "customizeHeadlines";
/**
* @hidden
*/
Action["PinboardInfo"] = "pinboardInfo";
Action["LiveboardInfo"] = "pinboardInfo";
Action["SendAnswerFeedback"] = "sendFeedback";
Action["DownloadEmbraceQueries"] = "downloadEmbraceQueries";
Action["Pin"] = "pin";
Action["AnalysisInfo"] = "analysisInfo";
Action["Subscription"] = "subscription";
Action["Explore"] = "explore";
Action["DrillInclude"] = "context-menu-item-include";
Action["DrillExclude"] = "context-menu-item-exclude";
Action["CopyToClipboard"] = "context-menu-item-copy-to-clipboard";
Action["CopyAndEdit"] = "context-menu-item-copy-and-edit";
Action["DrillEdit"] = "context-menu-item-edit";
Action["EditMeasure"] = "context-menu-item-edit-measure";
Action["Separator"] = "context-menu-item-separator";
Action["DrillDown"] = "DRILL";
Action["RequestAccess"] = "requestAccess";
Action["QueryDetailsButtons"] = "queryDetailsButtons";
/**
* @version SDK: 1.8.0 | ThoughtSpot: 8.1.0.cl
*/
Action["Monitor"] = "createMonitor";
/**
* @version SDK: 1.8.0 | ThoughtSpot: 8.1.0.cl
*/
Action["AnswerDelete"] = "onDeleteAnswer";
/**
* @version SDK: 1.8.0 | ThoughtSpot: 8.1.0.cl
*/
Action["AnswerChartSwitcher"] = "answerChartSwitcher";
/**
* @version SDK: 1.8.0 | ThoughtSpot: 8.1.0.cl
*/
Action["AddToFavorites"] = "addToFavorites";
/**
* @version SDK: 1.8.0 | ThoughtSpot: 8.1.0.cl
*/
Action["EditDetails"] = "editDetails";
})(exports.Action || (exports.Action = {}));
// eslint-disable-next-line no-shadow
var OperationType;
(function (OperationType) {
OperationType["GetChartWithData"] = "GetChartWithData";
OperationType["GetTableWithHeadlineData"] = "GetTableWithHeadlineData";
})(OperationType || (OperationType = {}));
const ERROR_MESSAGE = {
INVALID_THOUGHTSPOT_HOST: 'Error parsing ThoughtSpot host. Please provide a valid URL.',
LIVEBOARD_VIZ_ID_VALIDATION: 'Please provide either liveboardId or pinboardId',
};
/**
* Copyright (c) 2022
*
* Utilities related to reading configuration objects
*
* @summary Config-related utils
* @author Ayon Ghosh <ayon.ghosh@thoughtspot.com>
*/
const urlRegex = new RegExp([
'(^(https?:)//)?',
'(([^:/?#]*)(?::([0-9]+))?)',
'(/{0,1}[^?#]*)',
'(\\?[^#]*|)',
'(#.*|)$', // hash
].join(''));
/**
* Parse and construct the ThoughtSpot hostname or IP address
* from the embed configuration object.
* @param config
*/
const getThoughtSpotHost = (config) => {
const urlParts = config.thoughtSpotHost.match(urlRegex);
if (!urlParts) {
throw new Error(ERROR_MESSAGE.INVALID_THOUGHTSPOT_HOST);
}
const protocol = urlParts[2] || window.location.protocol;
const host = urlParts[3];
let path = urlParts[6];
// Lose the trailing / if any
if (path.charAt(path.length - 1) === '/') {
path = path.substring(0, path.length - 1);
}
// const urlParams = urlParts[7];
// const hash = urlParts[8];
return `${protocol}//${host}${path}`;
};
const getV2BasePath = (config) => {
if (config.basepath) {
return config.basepath;
}
const tsHost = getThoughtSpotHost(config);
// This is to handle when e2e's. Search is run on pods for comp-blink-test-pipeline
// with baseUrl=https://localhost:8443.
// This is to handle when the developer is developing in their local environment.
if (tsHost.includes('://localhost') && !tsHost.includes(':8443')) {
return '';
}
return 'v2';
};
/**
* It is a good idea to keep URLs under 2000 chars.
* If this is ever breached, since we pass view configuration through
* URL params, we would like to log a warning.
* Reference: https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers
*/
const URL_MAX_LENGTH = 2000;
/**
* The default CSS dimensions of the embedded app
*/
const DEFAULT_EMBED_WIDTH = '100%';
const DEFAULT_EMBED_HEIGHT = '100%';
var Config = {
DEBUG: false,
LIB_VERSION: '2.41.0'
};
// since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
var window$1;
if (typeof(window) === 'undefined') {
var loc = {
hostname: ''
};
window$1 = {
navigator: { userAgent: '' },
document: {
location: loc,
referrer: ''
},
screen: { width: 0, height: 0 },
location: loc
};
} else {
window$1 = window;
}
/*
* Saved references to long variable names, so that closure compiler can
* minimize file size.
*/
var ArrayProto = Array.prototype;
var FuncProto = Function.prototype;
var ObjProto = Object.prototype;
var slice = ArrayProto.slice;
var toString = ObjProto.toString;
var hasOwnProperty = ObjProto.hasOwnProperty;
var windowConsole = window$1.console;
var navigator$1 = window$1.navigator;
var document$1 = window$1.document;
var windowOpera = window$1.opera;
var screen = window$1.screen;
var userAgent = navigator$1.userAgent;
var nativeBind = FuncProto.bind;
var nativeForEach = ArrayProto.forEach;
var nativeIndexOf = ArrayProto.indexOf;
var nativeMap = ArrayProto.map;
var nativeIsArray = Array.isArray;
var breaker = {};
var _ = {
trim: function(str) {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill
return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
}
};
// Console override
var console$1 = {
/** @type {function(...*)} */
log: function() {
if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
try {
windowConsole.log.apply(windowConsole, arguments);
} catch (err) {
_.each(arguments, function(arg) {
windowConsole.log(arg);
});
}
}
},
/** @type {function(...*)} */
warn: function() {
if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
var args = ['Mixpanel warning:'].concat(_.toArray(arguments));
try {
windowConsole.warn.apply(windowConsole, args);
} catch (err) {
_.each(args, function(arg) {
windowConsole.warn(arg);
});
}
}
},
/** @type {function(...*)} */
error: function() {
if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
var args = ['Mixpanel error:'].concat(_.toArray(arguments));
try {
windowConsole.error.apply(windowConsole, args);
} catch (err) {
_.each(args, function(arg) {
windowConsole.error(arg);
});
}
}
},
/** @type {function(...*)} */
critical: function() {
if (!_.isUndefined(windowConsole) && windowConsole) {
var args = ['Mixpanel error:'].concat(_.toArray(arguments));
try {
windowConsole.error.apply(windowConsole, args);
} catch (err) {
_.each(args, function(arg) {
windowConsole.error(arg);
});
}
}
}
};
var log_func_with_prefix = function(func, prefix) {
return function() {
arguments[0] = '[' + prefix + '] ' + arguments[0];
return func.apply(console$1, arguments);
};
};
var console_with_prefix = function(prefix) {
return {
log: log_func_with_prefix(console$1.log, prefix),
error: log_func_with_prefix(console$1.error, prefix),
critical: log_func_with_prefix(console$1.critical, prefix)
};
};
// UNDERSCORE
// Embed part of the Underscore Library
_.bind = function(func, context) {
var args, bound;
if (nativeBind && func.bind === nativeBind) {
return nativeBind.apply(func, slice.call(arguments, 1));
}
if (!_.isFunction(func)) {
throw new TypeError();
}
args = slice.call(arguments, 2);
bound = function() {
if (!(this instanceof bound)) {
return func.apply(context, args.concat(slice.call(arguments)));
}
var ctor = {};
ctor.prototype = func.prototype;
var self = new ctor();
ctor.prototype = null;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) {
return result;
}
return self;
};
return bound;
};
_.bind_instance_methods = function(obj) {
for (var func in obj) {
if (typeof(obj[func]) === 'function') {
obj[func] = _.bind(obj[func], obj);
}
}
};
/**
* @param {*=} obj
* @param {function(...*)=} iterator
* @param {Object=} context
*/
_.each = function(obj, iterator, context) {
if (obj === null || obj === undefined) {
return;
}
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) {
return;
}
}
} else {
for (var key in obj) {
if (hasOwnProperty.call(obj, key)) {
if (iterator.call(context, obj[key], key, obj) === breaker) {
return;
}
}
}
}
};
_.escapeHTML = function(s) {
var escaped = s;
if (escaped && _.isString(escaped)) {
escaped = escaped
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
return escaped;
};
_.extend = function(obj) {
_.each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
if (source[prop] !== void 0) {
obj[prop] = source[prop];
}
}
});
return obj;
};
_.isArray = nativeIsArray || function(obj) {
return toString.call(obj) === '[object Array]';
};
// from a comment on http://dbj.org/dbj/?p=286
// fails on only one very rare and deliberate custom object:
// var bomb = { toString : undefined, valueOf: function(o) { return "function BOMBA!"; }};
_.isFunction = function(f) {
try {
return /^\s*\bfunction\b/.test(f);
} catch (x) {
return false;
}
};
_.isArguments = function(obj) {
return !!(obj && hasOwnProperty.call(obj, 'callee'));
};
_.toArray = function(iterable) {
if (!iterable) {
return [];
}
if (iterable.toArray) {
return iterable.toArray();
}
if (_.isArray(iterable)) {
return slice.call(iterable);
}
if (_.isArguments(iterable)) {
return slice.call(iterable);
}
return _.values(iterable);
};
_.map = function(arr, callback, context) {
if (nativeMap && arr.map === nativeMap) {
return arr.map(callback, context);
} else {
var results = [];
_.each(arr, function(item) {
results.push(callback.call(context, item));
});
return results;
}
};
_.keys = function(obj) {
var results = [];
if (obj === null) {
return results;
}
_.each(obj, function(value, key) {
results[results.length] = key;
});
return results;
};
_.values = function(obj) {
var results = [];
if (obj === null) {
return results;
}
_.each(obj, function(value) {
results[results.length] = value;
});
return results;
};
_.include = function(obj, target) {
var found = false;
if (obj === null) {
return found;
}
if (nativeIndexOf && obj.indexOf === nativeIndexOf) {
return obj.indexOf(target) != -1;
}
_.each(obj, function(value) {
if (found || (found = (value === target))) {
return breaker;
}
});
return found;
};
_.includes = function(str, needle) {
return str.indexOf(needle) !== -1;
};
// Underscore Addons
_.inherit = function(subclass, superclass) {
subclass.prototype = new superclass();
subclass.prototype.constructor = subclass;
subclass.superclass = superclass.prototype;
return subclass;
};
_.isObject = function(obj) {
return (obj === Object(obj) && !_.isArray(obj));
};
_.isEmptyObject = function(obj) {
if (_.isObject(obj)) {
for (var key in obj) {
if (hasOwnProperty.call(obj, key)) {
return false;
}
}
return true;
}
return false;
};
_.isUndefined = function(obj) {
return obj === void 0;
};
_.isString = function(obj) {
return toString.call(obj) == '[object String]';
};
_.isDate = function(obj) {
return toString.call(obj) == '[object Date]';
};
_.isNumber = function(obj) {
return toString.call(obj) == '[object Number]';
};
_.isElement = function(obj) {
return !!(obj && obj.nodeType === 1);
};
_.encodeDates = function(obj) {
_.each(obj, function(v, k) {
if (_.isDate(v)) {
obj[k] = _.formatDate(v);
} else if (_.isObject(v)) {
obj[k] = _.encodeDates(v); // recurse
}
});
return obj;
};
_.timestamp = function() {
Date.now = Date.now || function() {
return +new Date;
};
return Date.now();
};
_.formatDate = function(d) {
// YYYY-MM-DDTHH:MM:SS in UTC
function pad(n) {
return n < 10 ? '0' + n : n;
}
return d.getUTCFullYear() + '-' +
pad(d.getUTCMonth() + 1) + '-' +
pad(d.getUTCDate()) + 'T' +
pad(d.getUTCHours()) + ':' +
pad(d.getUTCMinutes()) + ':' +
pad(d.getUTCSeconds());
};
_.safewrap = function(f) {
return function() {
try {
return f.apply(this, arguments);
} catch (e) {
console$1.critical('Implementation error. Please turn on debug and contact support@mixpanel.com.');
if (Config.DEBUG){
console$1.critical(e);
}
}
};
};
_.safewrap_class = function(klass, functions) {
for (var i = 0; i < functions.length; i++) {
klass.prototype[functions[i]] = _.safewrap(klass.prototype[functions[i]]);
}
};
_.safewrap_instance_methods = function(obj) {
for (var func in obj) {
if (typeof(obj[func]) === 'function') {
obj[func] = _.safewrap(obj[func]);
}
}
};
_.strip_empty_properties = function(p) {
var ret = {};
_.each(p, function(v, k) {
if (_.isString(v) && v.length > 0) {
ret[k] = v;
}
});
return ret;
};
/*
* this function returns a copy of object after truncating it. If
* passed an Array or Object it will iterate through obj and
* truncate all the values recursively.
*/
_.truncate = function(obj, length) {
var ret;
if (typeof(obj) === 'string') {
ret = obj.slice(0, length);
} else if (_.isArray(obj)) {
ret = [];
_.each(obj, function(val) {
ret.push(_.truncate(val, length));
});
} else if (_.isObject(obj)) {
ret = {};
_.each(obj, function(val, key) {
ret[key] = _.truncate(val, length);
});
} else {
ret = obj;
}
return ret;
};
_.JSONEncode = (function() {
return function(mixed_val) {
var value = mixed_val;
var quote = function(string) {
var escapable = /[\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; // eslint-disable-line no-control-regex
var meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"': '\\"',
'\\': '\\\\'
};
escapable.lastIndex = 0;
return escapable.test(string) ?
'"' + string.replace(escapable, function(a) {
var c = meta[a];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' :
'"' + string + '"';
};
var str = function(key, holder) {
var gap = '';
var indent = ' ';
var i = 0; // The loop counter.
var k = ''; // The member key.
var v = ''; // The member value.
var length = 0;
var mind = gap;
var partial = [];
var value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
case 'object':
// If the type is 'object', we might be dealing with an object or an array or
// null.
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' :
gap ? '[\n' + gap +
partial.join(',\n' + gap) + '\n' +
mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// Iterate through all of the keys in the object.
for (k in value) {
if (hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' :
gap ? '{' + partial.join(',') + '' +
mind + '}' : '{' + partial.join(',') + '}';
gap = mind;
return v;
}
};
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {
'': value
});
};
})();
/**
* From https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js
* Slightly modified to throw a real Error rather than a POJO
*/
_.JSONDecode = (function() {
var at, // The index of the current character
ch, // The current character
escapee = {
'"': '"',
'\\': '\\',
'/': '/',
'b': '\b',
'f': '\f',
'n': '\n',
'r': '\r',
't': '\t'
},
text,
error = function(m) {
var e = new SyntaxError(m);
e.at = at;
e.text = text;
throw e;
},
next = function(c) {
// If a c parameter is provided, verify that it matches the current character.
if (c && c !== ch) {
error('Expected \'' + c + '\' instead of \'' + ch + '\'');
}
// Get the next character. When there are no more characters,
// return the empty string.
ch = text.charAt(at);
at += 1;
return ch;
},
number = function() {
// Parse a number value.
var number,
string = '';
if (ch === '-') {
string = '-';
next('-');
}
while (ch >= '0' && ch <= '9') {
string += ch;
next();
}
if (ch === '.') {
string += '.';
while (next() && ch >= '0' && ch <= '9') {
string += ch;
}
}
if (ch === 'e' || ch === 'E') {
string += ch;
next();
if (ch === '-' || ch === '+') {
string += ch;
next();
}
while (ch >= '0' && ch <= '9') {
string += ch;
next();
}
}
number = +string;
if (!isFinite(number)) {
error('Bad number');
} else {
return number;
}
},
string = function() {
// Parse a string value.
var hex,
i,
string = '',
uffff;
// When parsing for string values, we must look for " and \ characters.
if (ch === '"') {
while (next()) {
if (ch === '"') {
next();
return string;
}
if (ch === '\\') {
next();
if (ch === 'u') {
uffff = 0;
for (i = 0; i < 4; i += 1) {
hex = parseInt(next(), 16);
if (!isFinite(hex)) {
break;
}
uffff = uffff * 16 + hex;
}
string += String.fromCharCode(uffff);
} else if (typeof escapee[ch] === 'string') {
string += escapee[ch];
} else {
break;
}
} else {
string += ch;
}
}
}
error('Bad string');
},
white = function() {
// Skip whitespace.
while (ch && ch <= ' ') {
next();
}
},
word = function() {
// true, false, or null.
switch (ch) {
case 't':
next('t');
next('r');
next('u');
next('e');
return true;
case 'f':
next('f');
next('a');
next('l');
next('s');
next('e');
return false;
case 'n':
next('n');
next('u');
next('l');
next('l');
return null;
}
error('Unexpected "' + ch + '"');
},
value, // Placeholder for the value function.
array = function() {
// Parse an array value.
var array = [];
if (ch === '[') {
next('[');
white();
if (ch === ']') {
next(']');
return array; // empty array
}
while (ch) {
array.push(value());
white();
if (ch === ']') {
next(']');
return array;
}
next(',');
white();
}
}
error('Bad array');
},
object = function() {
// Parse an object value.
var key,
object = {};
if (ch === '{') {
next('{');
white();
if (ch === '}') {
next('}');
return object; // empty object
}
while (ch) {
key = string();
white();
next(':');
if (Object.hasOwnProperty.call(object, key)) {
error('Duplicate key "' + key + '"');
}
object[key] = value();
white();
if (ch === '}') {
next('}');
return object;
}
next(',');
white();
}
}
error('Bad object');
};
value = function() {
// Parse a JSON value. It could be an object, an array, a string,
// a number, or a word.
white();
switch (ch) {
case '{':
return object();
case '[':
return array();
case '"':
return string();
case '-':
return number();
default:
return ch >= '0' && ch <= '9' ? number() : word();
}
};
// Return the json_parse function. It will have access to all of the
// above functions and variables.
return function(source) {
var result;
text = source;
at = 0;
ch = ' ';
result = value();
white();
if (ch) {
error('Syntax error');
}
return result;
};
})();
_.base64Encode = function(data) {
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
ac = 0,
enc = '',
tmp_arr = [];
if (!data) {
return data;
}
data = _.utf8Encode(data);
do { // pack three octets into four hexets
o1 = data.charCodeAt(i++);
o2 = data.charCodeAt(i++);
o3 = data.charCodeAt(i++);
bits = o1 << 16 | o2 << 8 | o3;
h1 = bits >> 18 & 0x3f;
h2 = bits >> 12 & 0x3f;
h3 = bits >> 6 & 0x3f;
h4 = bits & 0x3f;
// use hexets to index into b64, and append result to encoded string
tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
} while (i < data.length);
enc = tmp_arr.join('');
switch (data.length % 3) {
case 1:
enc = enc.slice(0, -2) + '==';
break;
case 2:
enc = enc.slice(0, -1) + '=';
break;
}
return enc;
};
_.utf8Encode = function(string) {
string = (string + '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
var utftext = '',
start,
end;
var stringl = 0,
n;