UNPKG

@preset-sdk/embedded

Version:

Frontend SDK for embedding Preset data analytics into your own application

213 lines (167 loc) 6.54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports._initComms = _initComms; exports.embedDashboard = embedDashboard; var _switchboard = require("@superset-ui/switchboard"); var _const = require("./const"); var _guestTokenRefresh = require("./guestTokenRefresh"); var _polyfills = require("./polyfills"); /** * Embeds a Superset dashboard into the page using an iframe. */ async function embedDashboard(_ref) { let { id, supersetDomain, mountPoint, fetchGuestToken, dashboardUiConfig, debug = false, iframeTitle = "Embedded Dashboard", iframeSandboxExtras = [], referrerPolicy } = _ref; function log() { if (debug) { for (var _len = arguments.length, info = new Array(_len), _key = 0; _key < _len; _key++) { info[_key] = arguments[_key]; } console.debug(`[preset-frontend-sdk][dashboard ${id}]`, ...info); } } log('embedding'); // Polyfill replaceChildren (0, _polyfills.applyReplaceChildrenPolyfill)(); if (supersetDomain.endsWith("/")) { supersetDomain = supersetDomain.slice(0, -1); } function calculateConfig() { let configNumber = 0; if (dashboardUiConfig) { if (dashboardUiConfig.hideTitle) { configNumber += 1; } if (dashboardUiConfig.hideTab) { configNumber += 2; } if (dashboardUiConfig.hideChartControls) { configNumber += 8; } if (dashboardUiConfig.emitDataMasks) { configNumber += 16; } if (dashboardUiConfig.showRowLimitWarning) { configNumber += 32; } } return configNumber; } async function mountIframe() { return new Promise(resolve => { const iframe = document.createElement('iframe'); const dashboardConfigUrlParams = dashboardUiConfig ? { uiConfig: `${calculateConfig()}` } : undefined; const filterConfig = dashboardUiConfig?.filters || {}; const filterConfigKeys = Object.keys(filterConfig); const filterConfigUrlParams = Object.fromEntries(filterConfigKeys.map(key => [_const.DASHBOARD_UI_FILTER_CONFIG_URL_PARAM_KEY[key], filterConfig[key]])); // Allow url query parameters from dashboardUiConfig.urlParams to override the ones from filterConfig const urlParams = { ...dashboardConfigUrlParams, ...filterConfigUrlParams, ...dashboardUiConfig?.urlParams }; const urlParamsString = Object.keys(urlParams).length ? '?' + new URLSearchParams(urlParams).toString() : ''; // setup the iframe's sandbox configuration iframe.sandbox.add("allow-same-origin"); // needed for postMessage to work iframe.sandbox.add("allow-scripts"); // obviously the iframe needs scripts iframe.sandbox.add("allow-presentation"); // for fullscreen charts iframe.sandbox.add("allow-downloads"); // for downloading charts as image iframe.sandbox.add("allow-top-navigation"); // for links to open iframe.sandbox.add("allow-forms"); // for forms to submit iframe.sandbox.add("allow-popups"); // for exporting charts as csv // additional sandbox props iframeSandboxExtras.forEach(key => { iframe.sandbox.add(key); }); // force a specific refererPolicy to be used in the iframe request if (referrerPolicy) { iframe.referrerPolicy = referrerPolicy; } // add the event listener before setting src, to be 100% sure that we capture the load event iframe.addEventListener('load', () => { const switchboard = _initComms(iframe.contentWindow, supersetDomain, debug); log('sent message channel to the iframe'); resolve(switchboard); }); iframe.src = `${supersetDomain}/embedded/${id}${urlParamsString}`; iframe.title = iframeTitle; mountPoint?.replaceChildren(iframe); log('placed the iframe'); }); } const [guestToken, ourPort] = await Promise.all([fetchGuestToken(), mountIframe()]); let refreshGuestTokenInterval; ourPort.emit('guestToken', { guestToken }); log('sent guest token'); async function refreshGuestToken() { const newGuestToken = await fetchGuestToken(); ourPort.emit('guestToken', { guestToken: newGuestToken }); refreshGuestTokenInterval = setTimeout(refreshGuestToken, (0, _guestTokenRefresh.getGuestTokenRefreshTiming)(newGuestToken)); } refreshGuestTokenInterval = setTimeout(refreshGuestToken, (0, _guestTokenRefresh.getGuestTokenRefreshTiming)(guestToken)); function unmount() { log('unmounting'); mountPoint?.replaceChildren(); clearTimeout(refreshGuestTokenInterval); } const getScrollSize = () => ourPort.get('getScrollSize'); const getDashboardPermalink = anchor => ourPort.get('getDashboardPermalink', { anchor }); const getActiveTabs = () => ourPort.get('getActiveTabs'); const getDataMask = () => ourPort.get('getDataMask'); const observeDataMask = callbackFn => { ourPort.start(); ourPort.defineMethod('observeDataMask', callbackFn); }; const setThemeConfig = async themeConfig => { try { ourPort.emit('setThemeConfig', { themeConfig }); log('Theme config sent successfully (or at least message dispatched)'); } catch (error) { log('Error sending theme config. Ensure the iframe side implements the "setThemeConfig" method.'); throw error; } }; return { getScrollSize, unmount, getDashboardPermalink, getActiveTabs, observeDataMask, getDataMask, setThemeConfig }; } function _initComms(window, targetOrigin) { let debug = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; // MessageChannel allows us to send and receive messages smoothly between our window and the iframe // See https://developer.mozilla.org/en-US/docs/Web/API/Channel_Messaging_API const commsChannel = new MessageChannel(); const ourPort = commsChannel.port1; const theirPort = commsChannel.port2; // Send one of the message channel ports to the iframe to initialize embedded comms // See https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage // we know the content window isn't null because we are in the load event handler. window.postMessage({ type: _const.IFRAME_COMMS_MESSAGE_TYPE, handshake: "port transfer" }, targetOrigin, [theirPort]); // return our port from the promise return new _switchboard.Switchboard({ port: ourPort, name: 'preset-frontend-sdk', debug }); }