UNPKG

@thoughtspot/visual-embed-sdk

Version:
190 lines 9.62 kB
import { HostEvent } from '../../types'; import { processTrigger as processTriggerService } from '../../utils/processTrigger'; import { getEmbedConfig } from '../embedConfig'; import { isValidUpdateFiltersPayload, isValidDrillDownPayload, throwUpdateFiltersValidationError, throwDrillDownValidationError, } from './utils'; import { UIPassthroughEvent, } from './contracts'; /** * Maps HostEvent to its corresponding UIPassthroughEvent. * Includes both custom-handler events (Pin, SaveAnswer, UpdateFilters, DrillDown) * and getter events (GetAnswerSession, GetFilters, etc.) that use getDataWithPassthroughFallback. */ const PASSTHROUGH_MAP = { // Custom handlers (setters with special logic) [HostEvent.Pin]: UIPassthroughEvent.PinAnswerToLiveboard, [HostEvent.SaveAnswer]: UIPassthroughEvent.SaveAnswer, [HostEvent.UpdateFilters]: UIPassthroughEvent.UpdateFilters, [HostEvent.DrillDown]: UIPassthroughEvent.Drilldown, // Getters (use getDataWithPassthroughFallback) [HostEvent.GetAnswerSession]: UIPassthroughEvent.GetAnswerSession, [HostEvent.GetFilters]: UIPassthroughEvent.GetFilters, [HostEvent.GetIframeUrl]: UIPassthroughEvent.GetIframeUrl, [HostEvent.GetParameters]: UIPassthroughEvent.GetParameters, [HostEvent.GetTML]: UIPassthroughEvent.GetTML, [HostEvent.GetTabs]: UIPassthroughEvent.GetTabs, [HostEvent.getExportRequestForCurrentPinboard]: UIPassthroughEvent.GetExportRequestForCurrentPinboard, }; export class HostEventClient { constructor(iFrame) { /** Cached list of available UI passthrough keys from the embedded app */ this.availablePassthroughKeysCache = null; this.iFrame = iFrame; this.customHandlers = { [HostEvent.Pin]: (p, c) => this.handlePinEvent(p, c), [HostEvent.SaveAnswer]: (p, c) => this.handleSaveAnswerEvent(p, c), [HostEvent.UpdateFilters]: (p, c) => this.handleUpdateFiltersEvent(p, c), [HostEvent.DrillDown]: (p, c) => this.handleDrillDownEvent(p, c), }; } /** * A wrapper over process trigger to * @param {HostEvent} message Host event to send * @param {any} data Data to send with the host event * @returns {Promise<any>} - the response from the process trigger */ async processTrigger(message, data, context) { if (!this.iFrame) { throw new Error('Iframe element is not set'); } const thoughtspotHost = getEmbedConfig().thoughtSpotHost; return processTriggerService(this.iFrame, message, thoughtspotHost, data, context); } async handleHostEventWithParam(apiName, parameters, context) { var _a, _b, _c, _d; const response = (_b = (_a = (await this.triggerUIPassthroughApi(apiName, parameters, context))) === null || _a === void 0 ? void 0 : _a.find) === null || _b === void 0 ? void 0 : _b.call(_a, (r) => r.error || r.value); if (!response) { const error = `No answer found${parameters.vizId ? ` for vizId: ${parameters.vizId}` : ''}.`; throw { error }; } const errors = response.error || ((_c = response.value) === null || _c === void 0 ? void 0 : _c.errors) || ((_d = response.value) === null || _d === void 0 ? void 0 : _d.error); if (errors) { const message = typeof errors === 'string' ? errors : JSON.stringify(errors); throw { error: message }; } return { ...response.value }; } async hostEventFallback(hostEvent, data, context) { return this.processTrigger(hostEvent, data, context); } /** * For getter events that return data. Tries UI passthrough first; * if the app doesn't support it (no response data), falls back to * the legacy host event channel. Real errors are thrown as-is. */ async getDataWithPassthroughFallback(passthroughEvent, hostEvent, payload, context) { var _a, _b, _c; const response = await this.triggerUIPassthroughApi(passthroughEvent, payload || {}, context); const matched = (_a = response === null || response === void 0 ? void 0 : response.find) === null || _a === void 0 ? void 0 : _a.call(response, (r) => r.error || r.value); if (!matched) { return this.hostEventFallback(hostEvent, payload, context); } const errors = matched.error || ((_b = matched.value) === null || _b === void 0 ? void 0 : _b.errors) || ((_c = matched.value) === null || _c === void 0 ? void 0 : _c.error); if (errors) { const message = typeof errors === 'string' ? errors : JSON.stringify(errors); throw new Error(message); } return { ...matched.value }; } /** * Setter for the iframe element used for host events * @param {HTMLIFrameElement} iFrame - the iframe element to set */ setIframeElement(iFrame) { this.iFrame = iFrame; } /** * Fetches the list of available UI passthrough keys from the embedded app. * Result is cached for the session. Returns empty array on failure. */ async getAvailableUIPassthroughKeys(context) { var _a, _b; if (this.availablePassthroughKeysCache !== null) { return this.availablePassthroughKeysCache; } try { const response = await this.triggerUIPassthroughApi(UIPassthroughEvent.GetAvailableUIPassthroughs, {}, context); const matched = (_a = response === null || response === void 0 ? void 0 : response.find) === null || _a === void 0 ? void 0 : _a.call(response, (r) => r.value && !r.error); const keys = (_b = matched === null || matched === void 0 ? void 0 : matched.value) === null || _b === void 0 ? void 0 : _b.keys; this.availablePassthroughKeysCache = Array.isArray(keys) ? keys : []; return this.availablePassthroughKeysCache; } catch { return []; } } async triggerUIPassthroughApi(apiName, parameters, context) { const res = await this.processTrigger(HostEvent.UIPassthrough, { type: apiName, parameters, }, context); return res; } async handlePinEvent(payload, context) { var _a, _b; if (!payload || !('newVizName' in payload)) { return this.hostEventFallback(HostEvent.Pin, payload, context); } const formattedPayload = { ...payload, pinboardId: (_a = payload.liveboardId) !== null && _a !== void 0 ? _a : payload.pinboardId, newPinboardName: (_b = payload.newLiveboardName) !== null && _b !== void 0 ? _b : payload.newPinboardName, }; const data = await this.handleHostEventWithParam(UIPassthroughEvent.PinAnswerToLiveboard, formattedPayload, context); return { ...data, liveboardId: data.pinboardId, }; } async handleSaveAnswerEvent(payload, context) { var _a, _b, _c, _d; if (!payload || !('name' in payload) || !('description' in payload)) { // Save is the fallback for SaveAnswer return this.hostEventFallback(HostEvent.Save, payload, context); } const data = await this.handleHostEventWithParam(UIPassthroughEvent.SaveAnswer, payload, context); return { ...data, answerId: (_d = (_c = (_b = (_a = data === null || data === void 0 ? void 0 : data.saveResponse) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.Answer__save) === null || _c === void 0 ? void 0 : _c.answer) === null || _d === void 0 ? void 0 : _d.id, }; } handleUpdateFiltersEvent(payload, context) { if (!isValidUpdateFiltersPayload(payload)) { throwUpdateFiltersValidationError(); } return this.handleHostEventWithParam(UIPassthroughEvent.UpdateFilters, payload, context); } handleDrillDownEvent(payload, context) { if (!isValidDrillDownPayload(payload)) { throwDrillDownValidationError(); } return this.handleHostEventWithParam(UIPassthroughEvent.Drilldown, payload, context); } /** * Dispatches a host event using the appropriate channel: * 1. If the embedded app supports UI passthrough for this event, use it (custom handler or getter). * 2. Otherwise fall back to the legacy host event channel. * * @param hostEvent - The host event to trigger * @param payload - Optional payload for the event * @param context - Optional context (e.g. vizId) for scoped operations */ async triggerHostEvent(hostEvent, payload, context) { const customHandler = this.customHandlers[hostEvent]; const passthroughEvent = PASSTHROUGH_MAP[hostEvent]; // If embedded app supports passthrough but not this event, use legacy channel const keys = passthroughEvent ? await this.getAvailableUIPassthroughKeys(context) : []; if (passthroughEvent && keys.length > 0 && !keys.includes(passthroughEvent)) { return this.hostEventFallback(hostEvent, payload, context); } // Custom handler (setters) > getter passthrough > legacy fallback return (customHandler ? customHandler(payload, context) : passthroughEvent ? this.getDataWithPassthroughFallback(passthroughEvent, hostEvent, payload, context) : this.hostEventFallback(hostEvent, payload, context)); } } //# sourceMappingURL=host-event-client.js.map