@thoughtspot/visual-embed-sdk
Version:
ThoughtSpot Embed SDK
387 lines • 16.1 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.reset = exports.exportTML = exports.executeTML = exports.renderInQueue = exports.logout = exports.disableAutoLogin = exports.init = exports.getIsInitCalled = exports.getInitPromise = exports.createAndSetInitPromise = exports.prefetch = exports.handleAuth = exports.notifyLogout = exports.notifyAuthSuccess = exports.notifyAuthSDKSuccess = exports.notifyAuthFailure = exports.getAuthPromise = exports.authPromise = void 0;
const tslib_1 = require("tslib");
/* eslint-disable camelcase */
/* eslint-disable import/no-mutable-exports */
/**
* Copyright (c) 2022
*
* Base classes
* @summary Base classes
* @author Ayon Ghosh <ayon.ghosh@thoughtspot.com>
*/
const eventemitter3_1 = tslib_1.__importDefault(require("eventemitter3"));
const reporting_1 = require("../utils/reporting");
const logger_1 = require("../utils/logger");
const tokenizedFetch_1 = require("../tokenizedFetch");
const authService_1 = require("../utils/authService/authService");
const config_1 = require("../config");
const types_1 = require("../types");
const auth_1 = require("../auth");
Object.defineProperty(exports, "notifyAuthFailure", { enumerable: true, get: function () { return auth_1.notifyAuthFailure; } });
Object.defineProperty(exports, "notifyAuthSDKSuccess", { enumerable: true, get: function () { return auth_1.notifyAuthSDKSuccess; } });
Object.defineProperty(exports, "notifyAuthSuccess", { enumerable: true, get: function () { return auth_1.notifyAuthSuccess; } });
Object.defineProperty(exports, "notifyLogout", { enumerable: true, get: function () { return auth_1.notifyLogout; } });
require("../utils/with-resolvers-polyfill");
const mixpanel_service_1 = require("../mixpanel-service");
const embedConfig_1 = require("./embedConfig");
const utils_1 = require("../utils");
const resetServices_1 = require("../utils/resetServices");
const CONFIG_DEFAULTS = {
loginFailedMessage: 'Not logged in',
authTriggerText: 'Authorize',
authType: types_1.AuthType.None,
logLevel: types_1.LogLevel.ERROR,
};
const getAuthPromise = () => exports.authPromise;
exports.getAuthPromise = getAuthPromise;
/**
* Perform authentication on the ThoughtSpot app as applicable.
*/
const handleAuth = () => {
exports.authPromise = (0, auth_1.authenticate)((0, embedConfig_1.getEmbedConfig)());
exports.authPromise.then((isLoggedIn) => {
if (!isLoggedIn) {
(0, auth_1.notifyAuthFailure)(auth_1.AuthFailureType.SDK);
}
else {
// Post login service is called after successful login.
(0, auth_1.postLoginService)();
(0, auth_1.notifyAuthSDKSuccess)();
}
}, () => {
(0, auth_1.notifyAuthFailure)(auth_1.AuthFailureType.SDK);
});
return exports.authPromise;
};
exports.handleAuth = handleAuth;
const hostUrlToFeatureUrl = {
[types_1.PrefetchFeatures.SearchEmbed]: (url, flags) => `${url}v2/?${flags}#/embed/answer`,
[types_1.PrefetchFeatures.LiveboardEmbed]: (url, flags) => `${url}?${flags}`,
[types_1.PrefetchFeatures.FullApp]: (url, flags) => `${url}?${flags}`,
[types_1.PrefetchFeatures.VizEmbed]: (url, flags) => `${url}?${flags}`,
};
/**
* Prefetches static resources from the specified URL. Web browsers can then cache the
* prefetched resources and serve them from the user's local disk to provide faster access
* to your app.
* @param url The URL provided for prefetch
* @param prefetchFeatures Specify features which needs to be prefetched.
* @param additionalFlags This can be used to add any URL flag.
* @version SDK: 1.4.0 | ThoughtSpot: ts7.sep.cl, 7.2.1
* @group Global methods
*/
const prefetch = (url, prefetchFeatures, additionalFlags) => {
var _a;
if (url === '') {
// eslint-disable-next-line no-console
logger_1.logger.warn('The prefetch method does not have a valid URL');
}
else {
const features = prefetchFeatures || [types_1.PrefetchFeatures.FullApp];
let hostUrl = url || (0, embedConfig_1.getEmbedConfig)().thoughtSpotHost;
const prefetchFlags = {
[types_1.Param.EmbedApp]: true,
...(_a = (0, embedConfig_1.getEmbedConfig)()) === null || _a === void 0 ? void 0 : _a.additionalFlags,
...additionalFlags,
};
hostUrl = hostUrl[hostUrl.length - 1] === '/' ? hostUrl : `${hostUrl}/`;
Array.from(new Set(features
.map((feature) => hostUrlToFeatureUrl[feature](hostUrl, (0, utils_1.getQueryParamString)(prefetchFlags)))))
.forEach((prefetchUrl, index) => {
const iFrame = document.createElement('iframe');
iFrame.src = prefetchUrl;
iFrame.style.width = '0';
iFrame.style.height = '0';
iFrame.style.border = '0';
// Make it 'fixed' to keep it in a different stacking context.
// This should solve the focus behaviours inside the iframe from
// interfering with main body.
iFrame.style.position = 'fixed';
// Push it out of viewport.
iFrame.style.top = '100vh';
iFrame.style.left = '100vw';
iFrame.classList.add('prefetchIframe');
iFrame.classList.add(`prefetchIframeNum-${index}`);
document.body.appendChild(iFrame);
});
}
};
exports.prefetch = prefetch;
/**
*
* @param embedConfig
*/
function sanity(embedConfig) {
if (embedConfig.thoughtSpotHost === undefined) {
throw new Error('ThoughtSpot host not provided');
}
if (embedConfig.authType === types_1.AuthType.TrustedAuthToken) {
if (!embedConfig.authEndpoint && typeof embedConfig.getAuthToken !== 'function') {
throw new Error('Trusted auth should provide either authEndpoint or getAuthToken');
}
}
}
/**
*
* @param embedConfig
*/
function backwardCompat(embedConfig) {
const newConfig = { ...embedConfig };
if (embedConfig.noRedirect !== undefined && embedConfig.inPopup === undefined) {
newConfig.inPopup = embedConfig.noRedirect;
}
return newConfig;
}
const initFlagKey = 'initFlagKey';
const createAndSetInitPromise = () => {
const { promise: initPromise, resolve: initPromiseResolve,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
} = Promise.withResolvers();
const initFlagStore = {
initPromise,
isInitCalled: false,
initPromiseResolve,
};
(0, utils_1.storeValueInWindow)(initFlagKey, initFlagStore, {
// In case of diff imports the promise might be already set
ignoreIfAlreadyExists: true,
});
};
exports.createAndSetInitPromise = createAndSetInitPromise;
(0, exports.createAndSetInitPromise)();
const getInitPromise = () => { var _a; return (_a = (0, utils_1.getValueFromWindow)(initFlagKey)) === null || _a === void 0 ? void 0 : _a.initPromise; };
exports.getInitPromise = getInitPromise;
const getIsInitCalled = () => { var _a; return !!((_a = (0, utils_1.getValueFromWindow)(initFlagKey)) === null || _a === void 0 ? void 0 : _a.isInitCalled); };
exports.getIsInitCalled = getIsInitCalled;
/**
* Initializes the Visual Embed SDK globally and perform
* authentication if applicable. This function needs to be called before any ThoughtSpot
* component like Liveboard etc can be embedded. But need not wait for AuthEvent.SUCCESS
* to actually embed. That is handled internally.
* @param embedConfig The configuration object containing ThoughtSpot host,
* authentication mechanism and so on.
* @example
* ```js
* const authStatus = init({
* thoughtSpotHost: 'https://my.thoughtspot.cloud',
* authType: AuthType.None,
* });
* authStatus.on(AuthStatus.FAILURE, (reason) => { // do something here });
* ```
* @returns {@link AuthEventEmitter} event emitter which emits events on authentication success,
* failure and logout. See {@link AuthStatus}
* @version SDK: 1.0.0 | ThoughtSpot ts7.april.cl, 7.2.1
* @group Authentication / Init
*/
const init = (embedConfig) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
sanity(embedConfig);
(0, resetServices_1.resetAllCachedServices)();
embedConfig = (0, embedConfig_1.setEmbedConfig)(backwardCompat({
...CONFIG_DEFAULTS,
...embedConfig,
thoughtSpotHost: (0, config_1.getThoughtSpotHost)(embedConfig),
}));
(0, logger_1.setGlobalLogLevelOverride)(embedConfig.logLevel);
(0, reporting_1.registerReportingObserver)();
const authEE = new eventemitter3_1.default();
(0, auth_1.setAuthEE)(authEE);
(0, exports.handleAuth)();
const { password, ...configToTrack } = (0, embedConfig_1.getEmbedConfig)();
(0, mixpanel_service_1.uploadMixpanelEvent)(mixpanel_service_1.MIXPANEL_EVENT.VISUAL_SDK_CALLED_INIT, {
...configToTrack,
usedCustomizationSheet: ((_b = (_a = embedConfig.customizations) === null || _a === void 0 ? void 0 : _a.style) === null || _b === void 0 ? void 0 : _b.customCSSUrl) != null,
usedCustomizationVariables: ((_e = (_d = (_c = embedConfig.customizations) === null || _c === void 0 ? void 0 : _c.style) === null || _d === void 0 ? void 0 : _d.customCSS) === null || _e === void 0 ? void 0 : _e.variables) != null,
usedCustomizationRules: ((_h = (_g = (_f = embedConfig.customizations) === null || _f === void 0 ? void 0 : _f.style) === null || _g === void 0 ? void 0 : _g.customCSS) === null || _h === void 0 ? void 0 : _h.rules_UNSTABLE) != null,
usedCustomizationStrings: !!((_k = (_j = embedConfig.customizations) === null || _j === void 0 ? void 0 : _j.content) === null || _k === void 0 ? void 0 : _k.strings),
usedCustomizationIconSprite: !!((_l = embedConfig.customizations) === null || _l === void 0 ? void 0 : _l.iconSpriteUrl),
});
if ((0, embedConfig_1.getEmbedConfig)().callPrefetch) {
(0, exports.prefetch)((0, embedConfig_1.getEmbedConfig)().thoughtSpotHost);
}
// Resolves the promise created in the initPromiseKey
(0, utils_1.getValueFromWindow)(initFlagKey).initPromiseResolve(authEE);
(0, utils_1.getValueFromWindow)(initFlagKey).isInitCalled = true;
return authEE;
};
exports.init = init;
/**
*
*/
function disableAutoLogin() {
(0, embedConfig_1.getEmbedConfig)().autoLogin = false;
}
exports.disableAutoLogin = disableAutoLogin;
/**
* Logs out from ThoughtSpot. This also sets the autoLogin flag to false, to
* prevent the SDK from automatically logging in again.
*
* You can call the `init` method again to re login, if autoLogin is set to
* true in this second call it will be honored.
* @param doNotDisableAutoLogin This flag when passed will not disable autoLogin
* @returns Promise which resolves when logout completes.
* @version SDK: 1.10.1 | ThoughtSpot: 8.2.0.cl, 8.4.1-sw
* @group Global methods
*/
const logout = (doNotDisableAutoLogin = false) => {
if (!doNotDisableAutoLogin) {
disableAutoLogin();
}
return (0, auth_1.logout)((0, embedConfig_1.getEmbedConfig)()).then((isLoggedIn) => {
(0, auth_1.notifyLogout)();
return isLoggedIn;
});
};
exports.logout = logout;
let renderQueue = Promise.resolve();
/**
* Renders functions in a queue, resolves to next function only after the callback next
* is called
* @param fn The function being registered
*/
const renderInQueue = (fn) => {
const { queueMultiRenders = false } = (0, embedConfig_1.getEmbedConfig)();
if (queueMultiRenders) {
renderQueue = renderQueue.then(() => new Promise((res) => fn(res)));
return renderQueue;
}
// Sending an empty function to keep it consistent with the above usage.
return fn(() => { }); // eslint-disable-line @typescript-eslint/no-empty-function
};
exports.renderInQueue = renderInQueue;
/**
* Imports TML representation of the metadata objects into ThoughtSpot.
* @param data
* @returns imports TML data into ThoughtSpot
* @example
* ```js
* executeTML({
* //Array of metadata Tmls in string format
* metadata_tmls: [
* "'\''{\"guid\":\"9bd202f5-d431-44bf-9a07-b4f7be372125\",
* \"liveboard\":{\"name\":\"Parameters Liveboard\"}}'\''"
* ],
* import_policy: 'PARTIAL', // Specifies the import policy for the TML import.
* create_new: false, // If selected, creates TML objects with new GUIDs.
* }).then(result => {
* console.log(result);
* }).catch(error => {
* console.error(error);
* });
*```
* @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl
* @group Global methods
*/
const executeTML = async (data) => {
try {
sanity((0, embedConfig_1.getEmbedConfig)());
}
catch (err) {
return Promise.reject(err);
}
const { thoughtSpotHost, authType } = (0, embedConfig_1.getEmbedConfig)();
const headers = {
'Content-Type': 'application/json',
'x-requested-by': 'ThoughtSpot',
};
const payload = {
metadata_tmls: data.metadata_tmls,
import_policy: data.import_policy || 'PARTIAL',
create_new: data.create_new || false,
};
return (0, tokenizedFetch_1.tokenizedFetch)(`${thoughtSpotHost}${authService_1.EndPoints.EXECUTE_TML}`, {
method: 'POST',
headers,
body: JSON.stringify(payload),
credentials: 'include',
})
.then((response) => {
if (!response.ok) {
throw new Error(`Failed to import TML data: ${response.status} - ${response.statusText}`);
}
return response.json();
})
.catch((error) => {
throw error;
});
};
exports.executeTML = executeTML;
/**
* Exports TML representation of the metadata objects from ThoughtSpot in JSON or YAML
* format.
* @param data
* @returns exports TML data
* @example
* ```js
* exportTML({
* metadata: [
* {
* type: "LIVEBOARD", //Metadata Type
* identifier: "9bd202f5-d431-44bf-9a07-b4f7be372125" //Metadata Id
* }
* ],
* export_associated: false,//indicates whether to export associated metadata objects
* export_fqn: false, //Adds FQNs of the referenced objects.For example, if you are
* //exporting a Liveboard and its associated objects, the API
* //returns the Liveboard TML data with the FQNs of the referenced
* //worksheet. If the exported TML data includes FQNs, you don't need
* //to manually add FQNs of the referenced objects during TML import.
* edoc_format: "JSON" //It takes JSON or YAML value
* }).then(result => {
* console.log(result);
* }).catch(error => {
* console.error(error);
* });
* ```
* @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl
* @group Global methods
*/
const exportTML = async (data) => {
const { thoughtSpotHost, authType } = (0, embedConfig_1.getEmbedConfig)();
try {
sanity((0, embedConfig_1.getEmbedConfig)());
}
catch (err) {
return Promise.reject(err);
}
const payload = {
metadata: data.metadata,
export_associated: data.export_associated || false,
export_fqn: data.export_fqn || false,
edoc_format: data.edoc_format || 'YAML',
};
const headers = {
'Content-Type': 'application/json',
'x-requested-by': 'ThoughtSpot',
};
return (0, tokenizedFetch_1.tokenizedFetch)(`${thoughtSpotHost}${authService_1.EndPoints.EXPORT_TML}`, {
method: 'POST',
headers,
body: JSON.stringify(payload),
credentials: 'include',
})
.then((response) => {
if (!response.ok) {
throw new Error(`Failed to export TML: ${response.status} - ${response.statusText}`);
}
return response.json();
})
.catch((error) => {
throw error;
});
};
exports.exportTML = exportTML;
// For testing purposes only
/**
*
*/
function reset() {
(0, embedConfig_1.setEmbedConfig)({});
(0, auth_1.setAuthEE)(null);
exports.authPromise = null;
}
exports.reset = reset;
//# sourceMappingURL=base.js.map
;