UNPKG

@thoughtspot/visual-embed-sdk

Version:
368 lines 14.2 kB
/* eslint-disable camelcase */ /* eslint-disable import/no-mutable-exports */ /** * Copyright (c) 2022 * * Base classes * @summary Base classes * @author Ayon Ghosh <ayon.ghosh@thoughtspot.com> */ import EventEmitter from 'eventemitter3'; import { registerReportingObserver } from '../utils/reporting'; import { logger, setGlobalLogLevelOverride } from '../utils/logger'; import { tokenizedFetch } from '../tokenizedFetch'; import { EndPoints } from '../utils/authService/authService'; import { getThoughtSpotHost } from '../config'; import { AuthType, LogLevel, Param, PrefetchFeatures, } from '../types'; import { authenticate, logout as _logout, AuthFailureType, notifyAuthFailure, notifyAuthSDKSuccess, notifyAuthSuccess, notifyLogout, setAuthEE, postLoginService, } from '../auth'; import '../utils/with-resolvers-polyfill'; import { uploadMixpanelEvent, MIXPANEL_EVENT } from '../mixpanel-service'; import { getEmbedConfig, setEmbedConfig } from './embedConfig'; import { getQueryParamString, getValueFromWindow, storeValueInWindow } from '../utils'; import { resetAllCachedServices } from '../utils/resetServices'; const CONFIG_DEFAULTS = { loginFailedMessage: 'Not logged in', authTriggerText: 'Authorize', authType: AuthType.None, logLevel: LogLevel.ERROR, }; export let authPromise; export const getAuthPromise = () => authPromise; export { notifyAuthFailure, notifyAuthSDKSuccess, notifyAuthSuccess, notifyLogout, }; /** * Perform authentication on the ThoughtSpot app as applicable. */ export const handleAuth = () => { authPromise = authenticate(getEmbedConfig()); authPromise.then((isLoggedIn) => { if (!isLoggedIn) { notifyAuthFailure(AuthFailureType.SDK); } else { // Post login service is called after successful login. postLoginService(); notifyAuthSDKSuccess(); } }, () => { notifyAuthFailure(AuthFailureType.SDK); }); return authPromise; }; const hostUrlToFeatureUrl = { [PrefetchFeatures.SearchEmbed]: (url, flags) => `${url}v2/?${flags}#/embed/answer`, [PrefetchFeatures.LiveboardEmbed]: (url, flags) => `${url}?${flags}`, [PrefetchFeatures.FullApp]: (url, flags) => `${url}?${flags}`, [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 */ export const prefetch = (url, prefetchFeatures, additionalFlags) => { var _a; if (url === '') { // eslint-disable-next-line no-console logger.warn('The prefetch method does not have a valid URL'); } else { const features = prefetchFeatures || [PrefetchFeatures.FullApp]; let hostUrl = url || getEmbedConfig().thoughtSpotHost; const prefetchFlags = { [Param.EmbedApp]: true, ...(_a = 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, 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); }); } }; /** * * @param embedConfig */ function sanity(embedConfig) { if (embedConfig.thoughtSpotHost === undefined) { throw new Error('ThoughtSpot host not provided'); } if (embedConfig.authType === 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'; export 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, }; storeValueInWindow(initFlagKey, initFlagStore, { // In case of diff imports the promise might be already set ignoreIfAlreadyExists: true, }); }; createAndSetInitPromise(); export const getInitPromise = () => { var _a; return (_a = getValueFromWindow(initFlagKey)) === null || _a === void 0 ? void 0 : _a.initPromise; }; export const getIsInitCalled = () => { var _a; return !!((_a = getValueFromWindow(initFlagKey)) === null || _a === void 0 ? void 0 : _a.isInitCalled); }; /** * 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 */ export const init = (embedConfig) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; sanity(embedConfig); resetAllCachedServices(); embedConfig = setEmbedConfig(backwardCompat({ ...CONFIG_DEFAULTS, ...embedConfig, thoughtSpotHost: getThoughtSpotHost(embedConfig), })); setGlobalLogLevelOverride(embedConfig.logLevel); registerReportingObserver(); const authEE = new EventEmitter(); setAuthEE(authEE); handleAuth(); const { password, ...configToTrack } = getEmbedConfig(); uploadMixpanelEvent(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 (getEmbedConfig().callPrefetch) { prefetch(getEmbedConfig().thoughtSpotHost); } // Resolves the promise created in the initPromiseKey getValueFromWindow(initFlagKey).initPromiseResolve(authEE); getValueFromWindow(initFlagKey).isInitCalled = true; return authEE; }; /** * */ export function disableAutoLogin() { getEmbedConfig().autoLogin = false; } /** * 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 */ export const logout = (doNotDisableAutoLogin = false) => { if (!doNotDisableAutoLogin) { disableAutoLogin(); } return _logout(getEmbedConfig()).then((isLoggedIn) => { notifyLogout(); return isLoggedIn; }); }; 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 */ export const renderInQueue = (fn) => { const { queueMultiRenders = false } = 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 }; /** * 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 */ export const executeTML = async (data) => { try { sanity(getEmbedConfig()); } catch (err) { return Promise.reject(err); } const { thoughtSpotHost, authType } = 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 tokenizedFetch(`${thoughtSpotHost}${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 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 */ export const exportTML = async (data) => { const { thoughtSpotHost, authType } = getEmbedConfig(); try { sanity(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 tokenizedFetch(`${thoughtSpotHost}${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; }); }; // For testing purposes only /** * */ export function reset() { setEmbedConfig({}); setAuthEE(null); authPromise = null; } //# sourceMappingURL=base.js.map