UNPKG

@thoughtspot/visual-embed-sdk

Version:
145 lines 5.93 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.startAutoMCPFrameRenderer = void 0; const types_1 = require("../types"); const ts_embed_1 = require("./ts-embed"); const utils_1 = require("../utils"); /** * Starts an automatic renderer that watches the DOM for iframes containing * the `tsmcp=true` query parameter and replaces them with fully configured * ThoughtSpot embed iframes. The query parameter is automatically added by * the ThoughtSpot MCP server. * * A {@link MutationObserver} is set up on `document.body` to detect both * directly added iframes and iframes nested within added container elements. * Each matching iframe is replaced in-place with a new ThoughtSpot embed * iframe that merges the original iframe's query parameters with the SDK * embed parameters. * * Call {@link MutationObserver.disconnect | observer.disconnect()} on the * returned observer to stop monitoring the DOM. * * @param viewConfig - Optional configuration for the auto-rendered embeds. * Accepts all properties from {@link AutoMCPFrameRendererViewConfig}. * Defaults to an empty config. * @returns A {@link MutationObserver} instance that is actively observing * `document.body`. Disconnect it when monitoring is no longer needed. * * @example * ```js * import { startAutoMCPFrameRenderer } from '@thoughtspot/visual-embed-sdk'; * * // Start watching the DOM for tsmcp iframes * const observer = startAutoMCPFrameRenderer({ * // optional view config overrides * }); * * // Later, stop watching * observer.disconnect(); * ``` * * @example * Detailed example of how to use the auto-frame renderer: * [Python React Agent Simple UI](https://github.com/thoughtspot/developer-examples/tree/main/mcp/python-react-agent-simple-ui) */ function startAutoMCPFrameRenderer(viewConfig = {}) { const replaceWithMCPIframe = (iframe) => { const autoMCPFrameRenderer = new AutoFrameRenderer(viewConfig); autoMCPFrameRenderer.replaceIframe(iframe); }; const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { for (const node of Array.from(mutation.addedNodes)) { if (node instanceof HTMLIFrameElement && isTSMCPIframe(node)) { replaceWithMCPIframe(node); } if (node instanceof HTMLElement) { node.querySelectorAll('iframe').forEach((iframe) => { if (isTSMCPIframe(iframe)) { replaceWithMCPIframe(iframe); } }); } } } }); observer.observe(document.body, { childList: true, subtree: true }); return observer; } exports.startAutoMCPFrameRenderer = startAutoMCPFrameRenderer; function isTSMCPIframe(iframe) { try { const url = new URL(iframe.src); return url.searchParams.get(types_1.Param.Tsmcp) === 'true'; } catch (e) { // The iframe src might not be a valid URL (e.g., 'about:blank'). return false; } } /** * Embed component that automatically replaces a plain iframe with a * ThoughtSpot embed iframe. It merges the SDK's embed parameters with * the original iframe's query parameters (stripping the `tsmcp` marker) * and swaps the original iframe element in the DOM. * * This class is used internally by {@link startAutoMCPFrameRenderer} and * is not intended to be instantiated directly. */ class AutoFrameRenderer extends ts_embed_1.TsEmbed { constructor(viewConfig) { viewConfig.embedComponentType = 'auto-frame-renderer'; const container = document.createElement('div'); super(container, viewConfig); this.viewConfig = viewConfig; } /** * Builds the final iframe `src` by merging the SDK embed parameters * with the query parameters already present on the source iframe URL. * The `tsmcp` marker param is removed so it does not propagate to the * ThoughtSpot application. * * @param sourceSrc - The original iframe's `src` URL string. * @returns The constructed URL to use for the ThoughtSpot embed iframe. */ getMCPIframeSrc(sourceSrc) { const queryParams = this.getEmbedParamsObject(); const sourceURL = new URL(sourceSrc); const existingQueryParams = sourceURL.searchParams; const existingQueryParamsObject = Object.fromEntries(existingQueryParams); delete existingQueryParamsObject[types_1.Param.Tsmcp]; const mergedQueryParams = { ...queryParams, ...existingQueryParamsObject }; const mergedQueryParamsString = (0, utils_1.getQueryParamString)(mergedQueryParams); const frameSrc = `${this.getEmbedBasePath(mergedQueryParamsString)}${sourceURL.hash.replace('#', '')}`; return frameSrc; } /** * Overrides the base insertion behavior so the new embed iframe * replaces the original iframe in-place rather than being appended * to a container element. Falls back to the default behavior when * no iframe has been set for replacement. */ handleInsertionIntoDOM(child) { if (this.frameToReplace) { this.frameToReplace.replaceWith(child); } else { super.handleInsertionIntoDOM(child); } } /** * Replaces the given iframe with a new ThoughtSpot embed iframe. * * The original iframe's `src` is used to derive the embed URL, and * once the new iframe is rendered it takes the original's place in * the DOM tree. * * @param iframe - The existing `<iframe>` element to replace. */ async replaceIframe(iframe) { this.frameToReplace = iframe; const src = this.getMCPIframeSrc(iframe.src); await this.renderIFrame(src); } } //# sourceMappingURL=auto-frame-renderer.js.map