UNPKG

@1fe/shell

Version:

Frontend shell and runtime for 1FE micro-frontend applications

1,531 lines (1,497 loc) 88.6 kB
import 'systemjs/dist/extras/dynamic-import-maps.min.js'; import 'systemjs/dist/extras/global.min.js'; import 'systemjs/dist/extras/module-types.min.js'; import 'systemjs/dist/extras/named-register.min.js'; import 'systemjs/dist/extras/use-default.min.js'; import * as React from 'react'; import React__default, { useEffect, useRef, useCallback, useMemo, Suspense, memo, useState } from 'react'; import ReactDOMClient from 'react-dom/client'; import { RouterProvider, createBrowserRouter, useNavigate, useLocation } from 'react-router-dom'; import { isEmpty, merge, once, isObject, pickBy, template } from 'lodash'; import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import deepFreeze from 'deep-freeze'; import styled from '@emotion/styled'; import bowser from 'bowser'; import Emittery from 'emittery'; import store from 'store2'; import Cookies from 'js-cookie'; // src/shell/index.tsx var oneFEConfigs = null; var readOneFEShellConfigs = () => { if (isEmpty(oneFEConfigs)) { throw new Error("OneFE Shell configs have not been initialized."); } return oneFEConfigs; }; var setOneFEShellConfigs = (newConfigs) => { oneFEConfigs = newConfigs; }; // src/shell/configs/config-helpers.ts var generateWidgetConfigMap = (parsedWidgetConfigData) => new Map( parsedWidgetConfigData.map((widgetConfig) => [ widgetConfig.widgetId, widgetConfig ]) ); var getConfigArrFromGlobal = (configName) => { const defaultValue = configName === "dynamic-config" ? {} : []; try { const oneDsConfig = JSON.parse( document?.querySelector(`script[data-1fe-config-id="${configName}"]`)?.innerHTML || JSON.stringify(defaultValue) ) ?? defaultValue; return oneDsConfig; } catch (e) { return defaultValue; } }; var getConfigObjFromGlobal = (configName, overrideSelector = `script[data-1fe-config-id="${configName}"]`) => { try { const oneDsConfig = JSON.parse( document?.querySelector(overrideSelector)?.innerHTML ?? "{}" ) ?? {}; if (Object.keys(oneDsConfig).length === 0) { return {}; } return oneDsConfig; } catch (e) { return {}; } }; var PLUGIN_CONFIGS = generateWidgetConfigMap( getConfigArrFromGlobal("plugin-config") ); var WIDGET_CONFIGS = generateWidgetConfigMap( getConfigArrFromGlobal("widget-config") ); var ENVIRONMENT_CONFIG = getConfigObjFromGlobal("env-config"); var DYNAMIC_CONFIGS = getConfigArrFromGlobal( "dynamic-config" ); var LAZY_LOADED_LIB_CONFIGS = getConfigObjFromGlobal("lazy-loaded-libs-config"); var getWidgetConfigValues = (widgetConfigs) => Array.from(widgetConfigs.values()); // src/shell/constants/event-names.ts var SHELL_NAVIGATED_EVENT = "[shell] navigated"; // src/shell/utils/telemetry.ts var getShellLogger = () => { return readOneFEShellConfigs()?.shellLogger || { log: (logObject) => { console.log(logObject); }, error: (logObject) => { console.error(logObject); }, logPlatformUtilUsage: true, redactSensitiveData: true }; }; // ../../node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function(t2, e2) { return t2.__proto__ = e2, t2; }, _setPrototypeOf(t, e); } // ../../node_modules/@babel/runtime/helpers/esm/inheritsLoose.js function _inheritsLoose(t, o) { t.prototype = Object.create(o.prototype), t.prototype.constructor = t, _setPrototypeOf(t, o); } var changedArray = function changedArray2(a, b) { if (a === void 0) { a = []; } if (b === void 0) { b = []; } return a.length !== b.length || a.some(function(item, index) { return !Object.is(item, b[index]); }); }; var initialState = { error: null }; var ErrorBoundary = /* @__PURE__ */ function(_React$Component) { _inheritsLoose(ErrorBoundary2, _React$Component); function ErrorBoundary2() { var _this; for (var _len = arguments.length, _args = new Array(_len), _key = 0; _key < _len; _key++) { _args[_key] = arguments[_key]; } _this = _React$Component.call.apply(_React$Component, [this].concat(_args)) || this; _this.state = initialState; _this.resetErrorBoundary = function() { var _this$props; for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } _this.props.onReset == null ? void 0 : (_this$props = _this.props).onReset.apply(_this$props, args); _this.reset(); }; return _this; } ErrorBoundary2.getDerivedStateFromError = function getDerivedStateFromError(error) { return { error }; }; var _proto = ErrorBoundary2.prototype; _proto.reset = function reset() { this.setState(initialState); }; _proto.componentDidCatch = function componentDidCatch(error, info) { var _this$props$onError, _this$props2; (_this$props$onError = (_this$props2 = this.props).onError) == null ? void 0 : _this$props$onError.call(_this$props2, error, info); }; _proto.componentDidUpdate = function componentDidUpdate(prevProps, prevState) { var error = this.state.error; var resetKeys = this.props.resetKeys; if (error !== null && prevState.error !== null && changedArray(prevProps.resetKeys, resetKeys)) { var _this$props$onResetKe, _this$props3; (_this$props$onResetKe = (_this$props3 = this.props).onResetKeysChange) == null ? void 0 : _this$props$onResetKe.call(_this$props3, prevProps.resetKeys, resetKeys); this.reset(); } }; _proto.render = function render() { var error = this.state.error; var _this$props4 = this.props, fallbackRender = _this$props4.fallbackRender, FallbackComponent = _this$props4.FallbackComponent, fallback = _this$props4.fallback; if (error !== null) { var _props = { error, resetErrorBoundary: this.resetErrorBoundary }; if (/* @__PURE__ */ React.isValidElement(fallback)) { return fallback; } else if (typeof fallbackRender === "function") { return fallbackRender(_props); } else if (FallbackComponent) { return /* @__PURE__ */ React.createElement(FallbackComponent, _props); } else { throw new Error("react-error-boundary requires either a fallback, fallbackRender, or FallbackComponent prop"); } } return this.props.children; }; return ErrorBoundary2; }(React.Component); var OneDsErrorBoundary = ({ children, widget, fallbackComponent, onError, onRender }) => { const logger = getShellLogger(); useEffect(() => { if (onRender) { onRender(); } }, [onRender]); useEffect(() => { logger.log({ message: `[1FE Shell] Rendering widget `, widget }); }, [widget]); const handleError = (error, info) => { if (onError) { onError(error, info); } else { logger.error({ message: widget ? `[1FE-Shell] Failure with the following: ${widget?.plugin?.route} ${widget.widgetId} ${widget.version}` : `[1FE-Shell] An error has occured.`, error, info, widget }); } }; return ( // DRAGON! ErrorBoundaries need a fallback. // On a widget level we don't want to show a fallback // unless the parent Widget renders the fallback. This is because we want the error to bubble up // to the parent Widget so that it can decide what to do with the error. // // We have to find a better approach for this. /* @__PURE__ */ jsx(ErrorBoundary, { onError: handleError, fallback: fallbackComponent, children }) ); }; var GenericError = (errorProps) => { const { type = "error", plugin, message } = errorProps || {}; useEffect(() => { const shellLogger = getShellLogger(); shellLogger.log({ message: "[1FE-Shell] error page rendered", errorComponent: { type, plugin, message } }); }, []); const ErrorPageData = { error: { titleText: "An error has occurred", subText: "Make sure your connection is stable and try again", buttonText: "Try Again" }, notFound: { titleText: "Looks like this page is not here", subText: "Check your URL, or go back", buttonText: "Go Back" } }; const mainText = message ?? ErrorPageData[type].titleText; const subText = ErrorPageData[type].subText; return /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx("h1", { children: mainText }), /* @__PURE__ */ jsx("p", { children: subText }) ] }); }; var getGenericError = (genericErrorProps) => { return /* @__PURE__ */ jsx(GenericError, { ...genericErrorProps }); }; var RouteWrapper = ({ children, plugin }) => { const location = useLocation(); const logger = getShellLogger(); const previousPathRef = useRef(location?.pathname); useEffect(() => { window.dispatchEvent( new CustomEvent(SHELL_NAVIGATED_EVENT, { detail: location }) ); logger.log({ message: `[1FE-Shell] Route changed`, path: location?.pathname, from: previousPathRef.current, widgetId: plugin?.widgetId }); previousPathRef.current = location?.pathname; }, [location]); const handleError = (error, info) => { logger.error({ message: `[1FE-Shell] Unhandled Route Failure`, error, info, location }); }; const IS_PROD = ENVIRONMENT_CONFIG.isProduction; const getError = readOneFEShellConfigs()?.components?.getError || getGenericError; if (!IS_PROD) { return /* @__PURE__ */ jsx(Fragment, { children }); } return /* @__PURE__ */ jsx( OneDsErrorBoundary, { fallbackComponent: getError({ plugin }), onError: handleError, children } ); }; // src/shell/utils/url.ts var templatizeCDNUrl = ({ widgetId, widgetVersion, templateFilePath = "js/1fe-bundle.js" }) => { return new URL( `${DYNAMIC_CONFIGS.widgets.basePrefix}${widgetId}/${widgetVersion}/${templateFilePath}` ); }; var generateCDNUrl = (widget) => { return templatizeCDNUrl({ widgetId: widget.widgetId, widgetVersion: widget.version }); }; var getBaseHrefUrl = (doc = document) => { const baseHref = doc?.querySelector("base")?.getAttribute("href"); if (!baseHref) { const error = "missing href attribute on base html element"; throw new Error(error); } return baseHref; }; var basePathname = () => new URL(getBaseHrefUrl()).pathname; var getWidgetBundleCdnUrl = ({ widgetId, version }) => { const widgetBundlePath = "/js/1fe-bundle.js"; const baseUrl = `${DYNAMIC_CONFIGS.widgets.basePrefix}/${widgetId}/${version}`; return new URL(`${baseUrl}${widgetBundlePath}`); }; // src/shell/constants/shell.ts var getShellWidgetId = () => "@1fe/shell"; // ../1fe-server/src/server/constants/index.ts var PINNED_WIDGET_TYPE = "pinned"; // ../1fe-server/src/server/utils/widget-config-helpers.ts var isWidgetTypePinned = (widgetType) => widgetType === PINNED_WIDGET_TYPE; var getPinnedWidgets = (widgetConfig) => { return widgetConfig?.runtime?.dependsOn?.pinnedWidgets || []; }; var getRequestedWidgetConfigWithoutRuntimeConfig = ({ hostWidgetId, requestedWidgetId, widgetConfigs }) => { const hostWidgetConfig = widgetConfigs.get(hostWidgetId); const requestedWidgetConfig = widgetConfigs.get(requestedWidgetId); if (!requestedWidgetConfig) { console.error( "[1FE][platformProps.utils.widgets.get] Unable to find requested widget config in the global WIDGET_CONFIGS map. Returning empty object.", { hostWidgetId, requestedWidgetId } ); return { requestedWidgetConfig: {} }; } const requestedWidgetPinnedConfig = getPinnedWidgets(hostWidgetConfig).find( (pinnedWidget) => pinnedWidget.widgetId === requestedWidgetId ); if (requestedWidgetPinnedConfig) { if (!isWidgetTypePinned(requestedWidgetConfig.type)) { console.warn( "[platformProps.utils.widgets.get][PINNED_WIDGETS] Requested pinned widget is not a pinned widget. The 1fe shell will request the current version instead." ); return { requestedWidgetConfig }; } return { requestedWidgetConfig: { ...requestedWidgetConfig, version: requestedWidgetPinnedConfig.version }, type: PINNED_WIDGET_TYPE }; } return { requestedWidgetConfig }; }; // src/shell/constants/widgets.ts var PINNED_WIDGET_TYPE2 = "pinned"; var SYSTEM_WIDGET_TYPE = "system"; var WIDGET_MODULE_KEYS = { /** * this key indicates that the widget has variants. It is always going to be `true` for now. */ hasVariants: "__hasVariants", /** * The key that holds the function that returns the widget. */ getWidget: "__getWidget", /** * The key that holds the function that returns the variant. */ getVariant: "__getVariant" }; // src/shell/utils/widget-type.ts var isWidgetTypeSystem = (widgetType) => widgetType === SYSTEM_WIDGET_TYPE; var isWidgetTypePinned2 = (widgetType) => widgetType === PINNED_WIDGET_TYPE2; var isShellWidget = (widgetId) => widgetId === getShellWidgetId(); // src/shell/utils/dom-helpers.ts var injectPreloadTags = (preloadUrls = [], as = "script") => { return preloadUrls.forEach((url) => { const link = document.createElement("link"); link.href = url; link.setAttribute("as", as); link.setAttribute("rel", "preload"); link.setAttribute("crossorigin", ""); document.body.appendChild(link); }); }; // src/shell/platformProps/utils/widgets/internal/utils/widgetConfigUtils.ts var queueWidgetPreloadsIfFound = (hostWidget, requestedWidgetConfig) => { if (isShellWidget(hostWidget.widgetId)) { return; } const preloadArr = requestedWidgetConfig.runtime?.preload || []; const preloadWidgetArr = preloadArr.map((preload) => preload.widget).filter((e) => e); const preloadAPIGetArr = preloadArr.map((preload) => preload.apiGet).filter((e) => e); const widgetURLsToPreload = preloadWidgetArr?.reduce( (itr, preloadedWidgetId) => { const preloadedWidgetConfig = WIDGET_CONFIGS.get(preloadedWidgetId); if (!preloadedWidgetConfig) { return itr; } const { requestedWidgetConfig: widgetConfigPreloadedByRequestedWidget } = getRequestedWidgetConfigWithoutRuntimeConfig({ hostWidgetId: requestedWidgetConfig.widgetId, requestedWidgetId: preloadedWidgetConfig.widgetId, widgetConfigs: WIDGET_CONFIGS }); const requestedWidgetConfigBundleCdnUrl = getWidgetBundleCdnUrl({ widgetId: widgetConfigPreloadedByRequestedWidget.widgetId, version: widgetConfigPreloadedByRequestedWidget.version }); return [...itr, requestedWidgetConfigBundleCdnUrl]; }, [] ); if (widgetURLsToPreload.length) { injectPreloadTags( widgetURLsToPreload.map((url) => url.toString()), "script" ); } if (preloadAPIGetArr.length) { injectPreloadTags(preloadAPIGetArr, "fetch"); } }; var getRequestedWidgetVersionForConsole = (hostWidgetId, widgetRequest) => { if (!widgetRequest) { return "widgetRequest undefined"; } if (!("version" in widgetRequest)) { return widgetRequest; } const { type } = getRequestedWidgetConfigWithoutRuntimeConfig({ hostWidgetId, requestedWidgetId: widgetRequest.widgetId, widgetConfigs: WIDGET_CONFIGS }); const version = isWidgetTypePinned2(type) ? `${widgetRequest.version} (pinned)` : widgetRequest.version; const importMapOverrides = window.importMapOverrides?.getOverrideMap()?.imports || {}; return importMapOverrides[widgetRequest.widgetId] ? "overridden" : version; }; var getGenericLoader = () => /* @__PURE__ */ jsx("p", { children: "Loading..." }); // src/shell/platformProps/utils/widgets/internal/utils/constants.tsx var getDefaultWidgetOptions = () => { const getLoader = readOneFEShellConfigs()?.components?.getLoader || getGenericLoader; return { variantId: "default", Loader: getLoader() }; }; // src/shell/platformProps/utils/logPlatformUtilUsage.ts var logPlatformUtilUsage = ({ widgetId, utilNamespace, functionName, success, args, attributes, topLevelAttributes }) => { const shouldLogPlatformUtilUsage = readOneFEShellConfigs()?.shellLogger?.logPlatformUtilUsage || false; if (shouldLogPlatformUtilUsage) { getShellLogger().log({ message: `utils.${utilNamespace}.${functionName} called`, category: `utils.${utilNamespace}.${functionName}`, widgetId, metaData: { success, arguments: { ...args }, ...attributes }, ...topLevelAttributes }); } }; // src/shell/constants/event-bus.ts var ONE_FE_SHELL_ID = "1FE_SHELL"; // src/shell/utils/tree.ts var getNodeId = () => { const random = crypto.getRandomValues(new Uint32Array(1))[0]; return `node-${random}`; }; var TreeNode = class { constructor(key, type, data) { this.key = key; this.id = getNodeId(); this.type = type; this.parents = /* @__PURE__ */ new Set(); this.children = /* @__PURE__ */ new Set(); this._data = data; } _data; id; type; parents; children; get data() { return this._data; } update(data) { this._data = data; } addChild(child) { this.children.add(child.id); child.parents.add(this.id); } addParent(parent) { this.parents.add(parent.id); parent.children.add(this.id); } removeChild(child) { this.children.delete(child.id); child.parents.delete(this.id); } removeParent(parent) { this.parents.delete(parent.id); parent.children.delete(this.id); } }; var Tree = class { /** * Tracks all memory references to {@link TreeNode}s. */ _refs; /** * Tracks all {@link TreeNode}s by their key. */ keyMap; /** * An empty node that serves as the root of the tree. */ root; constructor() { this._refs = /* @__PURE__ */ new Map(); this.keyMap = /* @__PURE__ */ new Map(); this.root = new TreeNode("#", "1FE_SHELL", {}); } get refs() { return this._refs; } /** * Given a key, returns the {@link TreeNode} associated with it. * NOTE: There's no getById since the id is opaque. */ getByKey(key) { if (!key) { return; } const id = this.keyMap.get(key); if (id) { return this.refs.get(id); } } getById(id) { return this.refs.get(id); } /** * Adds a new node to the tree. */ add(key, data, nodeType, parent = this.root) { let node = this.getByKey(key); if (!node) { node = new TreeNode(key, nodeType, data); this.refs.set(node.id, node); this.keyMap.set(key, node.id); } else { node.update(data); } if (parent) { parent.addChild(node); } return node; } /** * Removes a node from the tree. */ remove(key) { const node = this.getByKey(key); if (!node) { return; } for (const child of node.children) { this.refs.get(child)?.removeParent(node); } for (const parent of node.parents) { this.refs.get(parent)?.removeChild(node); } this.refs.delete(node.id); this.keyMap.delete(key); } /** * Returns an iterator over all nodes in the tree. The iterator * will yield the nodes in depth-first order. */ *[Symbol.iterator]() { yield* this.dfs(); } /** * Returns an iterator over all nodes in the tree. The iterator * will yield the nodes in depth-first order. The iterator can be started * at any node in the tree by passing in the key of the node to start at. */ *dfs(key = "") { const node = this.getByKey(key) ?? this.root; const visited = /* @__PURE__ */ new Set(); const stack = [node]; while (stack.length) { const top = stack.pop(); if (!top) { return; } if (visited.has(top.id)) { continue; } visited.add(top.id); if (top !== this.root) { yield [top.key, top.data]; } top.children.forEach((id) => { const child = this.refs.get(id); if (child) { stack.push(child); } }); } } }; var widgetDependencyTree = new Tree(); var widgetContextDependencyTree = new Tree(); // src/shell/platformProps/context/getWidgetPath/index.ts var getWidgetPath = (widgetId) => { const path = []; let current = widgetDependencyTree.getByKey(widgetId); while (current != null) { path.push(current.data.widgetId); const parentNodeId = current.parents.keys().next().value; current = widgetDependencyTree.getById(parentNodeId); } return [...path, ONE_FE_SHELL_ID]; }; // src/shell/platformProps/utils/widgets/internal/utils/getComponentFromModule.ts async function getComponentFromModule({ options: { variantId = getDefaultWidgetOptions().variantId } = getDefaultWidgetOptions(), module, log }) { const isVariantWidget = module.default?.[WIDGET_MODULE_KEYS.hasVariants]; const isRequestingDefaultVariant = variantId === getDefaultWidgetOptions().variantId; const logSuccessfulGet = ({ isVariantWidget: isVariantWidget2, variantId: variantId2, isRequestingDefaultVariant: isRequestingDefaultVariant2 } = {}) => log("[UTILS_API][WIDGETS] successful widgets.get call", { ...isVariantWidget2 && { isVariantWidget: isVariantWidget2 }, ...variantId2 && { variantId: variantId2 }, ...isRequestingDefaultVariant2 && { isRequestingDefaultVariant: isRequestingDefaultVariant2 } }); if (isVariantWidget) { if (isRequestingDefaultVariant) { logSuccessfulGet({ isVariantWidget, isRequestingDefaultVariant, variantId }); return module.default[WIDGET_MODULE_KEYS.getWidget](); } else { logSuccessfulGet({ isVariantWidget, variantId }); return module.default[WIDGET_MODULE_KEYS.getVariant](variantId); } } logSuccessfulGet(); return module; } // src/shell/platformProps/utils/widgets/internal/utils/isSystem.ts var isSystemEnv = () => typeof window !== "undefined" && typeof System !== "undefined"; // src/shell/constants/search-params.ts var STATE = "state"; var WIDGET_URL_OVERRIDES = "widget_url_overrides"; var RUNTIME_CONFIG_OVERRIDES = "runtime_config_overrides"; var parseRuntimeConfig = ({ runtimeConfig, widgetConfig }) => { const parsedRuntimeConfig = { ...runtimeConfig }; if (parsedRuntimeConfig?.preload) { const parsedPreloads = parsedRuntimeConfig.preload.map((preloadObj) => { if ("apiGet" in preloadObj) { const templatizedApiGetUrl = template(preloadObj.apiGet); return { apiGet: templatizedApiGetUrl({ WIDGET_VERSION: widgetConfig.version, WIDGET_ID: widgetConfig.widgetId, // If 1fe-app is running locally, the environment is development. // There is no cdn for development, so we use integration instead. // TODO[post-mvp][1fe]: How do we consume this back in 1fe if development has it's own cdn? ENVIRONMENT: ENVIRONMENT_CONFIG.environment }) }; } return preloadObj; }); parsedRuntimeConfig.preload = parsedPreloads; } return parsedRuntimeConfig; }; // src/shell/utils/runtime-configs.ts var clearRuntimeConfigOverrides = () => { const url = new URL(window.location.href); url.searchParams.delete(RUNTIME_CONFIG_OVERRIDES); window.location.href = url.toString(); }; var getParsedRuntimeConfigOverrides = () => { try { const param = new URL(window.location.href).searchParams.get( RUNTIME_CONFIG_OVERRIDES ); return param ? JSON.parse(param) : {}; } catch (e) { console.error( `[1FE][RUNTIME_CONFIG_OVERRIDES] Unable to parse JSON in ${RUNTIME_CONFIG_OVERRIDES} param` ); return {}; } }; var overrideWidgetConfigRuntime = (widgetId, runtimeConfig) => { const widgetConfig = WIDGET_CONFIGS.get(widgetId); if (!widgetConfig) { console.warn( "[1FE][overrideWidgetConfigRuntime] The key for you import map override does not exist in the global WIDGET_CONFIGS map. This could be because of a typo in a widgetId or because you are overriding a library." ); return; } WIDGET_CONFIGS.set(widgetId, { ...widgetConfig, runtime: parseRuntimeConfig({ runtimeConfig, widgetConfig }) }); }; var widgetRuntimeConfigUrlFilename = "widget-runtime-config.json"; var getRuntimeConfigUrlFromBundleUrl = (bundleUrl) => bundleUrl.replace("js/1fe-bundle.js", widgetRuntimeConfigUrlFilename); // src/shell/init/import-map-ui.ts var isOverrideActive = (element) => { const isActiveImportMapOverride = element.classList?.value.includes( "imo-current-override" ); return isActiveImportMapOverride || !isEmpty(getParsedRuntimeConfigOverrides()); }; var selectImportMapOverrideUi = () => document.querySelector("import-map-overrides-full")?.shadowRoot; var getImportMapOverridesButton = () => document?.getElementsByTagName("import-map-overrides-full")[0]?.shadowRoot?.querySelector("button"); var isOverrideElementActive = () => { const element = getImportMapOverridesButton(); if (!element) { return false; } return isOverrideActive(element); }; var animateImportMapButtonIfOverrides = () => { const colors = ["red", "salmon"]; let colorIndex = 0; let animationInterval = null; let scale = 1; setInterval(() => { const element = document?.getElementsByTagName("import-map-overrides-full")[0]?.shadowRoot?.querySelector("button"); if (!element) { return; } const isActiveOverride = isOverrideActive(element); if (isActiveOverride && !animationInterval) { const animationSpeed = "0.75s"; element.style.transition = `background-color ${animationSpeed}, transform ${animationSpeed}`; const tooltip = document.createElement("span"); tooltip.id = "activeImportMapTooltip"; tooltip.textContent = "You have active overrides"; Object.assign(tooltip.style, { display: "none", position: "absolute", bottom: "75px", right: "10px", padding: "10px", width: "auto", fontSize: "16", backgroundColor: "#F0EAD6", color: "black", borderRadius: "5px" }); document.body.appendChild(tooltip); element.addEventListener("mouseover", () => { tooltip.style.display = "block"; }); element.addEventListener("mouseout", () => { tooltip.style.display = "none"; }); animationInterval = window.setInterval(() => { element.style.backgroundColor = colors[colorIndex]; colorIndex = (colorIndex + 1) % colors.length; scale = scale == 1 ? 1.25 : 1; element.style.transform = `scale(${scale})`; }, 750); } else if (!isActiveOverride && animationInterval) { clearInterval(animationInterval); animationInterval = null; element.style.transition = ""; element.style.backgroundColor = "navajowhite"; element.style.transform = "scale(1)"; const tooltip = document.getElementById("activeImportMapTooltip"); if (tooltip) { document.body.removeChild(tooltip); } } }, 100); }; var copyImportMapOverridesButton = (textContent, onClick, applyCustomStyling = (button) => button) => { const importMapOverrides = document?.getElementsByTagName("import-map-overrides-full")[0]?.shadowRoot?.querySelector(".imo-table-header-actions"); if (!importMapOverrides) { return; } const divElement = document.createElement("div"); divElement.className = "imo-add-new"; const copyButton = document.createElement("button"); copyButton.textContent = textContent; copyButton.addEventListener("click", onClick); applyCustomStyling(copyButton); divElement.appendChild(copyButton); importMapOverrides?.appendChild(divElement); }; var deleteImportMapOverridesButton = () => { const element = getImportMapOverridesButton(); if (element) { element.remove(); } }; var initializeImportMapOverridesReskin = () => { const IS_PROD = ENVIRONMENT_CONFIG.isProduction; const enableUI = DYNAMIC_CONFIGS?.devtools?.importMapOverrides?.enableUI || true; if (IS_PROD || enableUI === false) { deleteImportMapOverridesButton(); return; } animateImportMapButtonIfOverrides(); const overrideShadowRoot = selectImportMapOverrideUi(); if (!overrideShadowRoot) { return; } const shadowRootCss = new CSSStyleSheet(); shadowRootCss.replaceSync(` .imo-popup { color: #343434; background: rgb(255, 255, 255); font-family: "DS Indigo", DSIndigo, Helvetica, Arial, sans-serif; border-top: 4px solid rgb(38, 70, 83); } .imo-header a {color: rgb(42, 157, 143);} .imo-table-header-actions { padding: 2rem; border: 2px dashed rgb(42, 157, 143); display: flex; } .imo-list-search { flex-grow: 1; height: 35px; box-sizing: border-box; font-family: inherit; font-size: 14px; letter-spacing: .16px; border-radius: 0; outline: 2px solid transparent; outline-offset: -2px; border: none; border-bottom: 1px solid #8d8d8d; background-color: #f4f4f4; padding: 0 16px; color: #161616; } .imo-list-container button { height: 35px; } .imo-add-new button { cursor: pointer; border-radius: 8px; border: 1px solid #666; background: #f2f2f2; color: #232323; } .imo-header .imo-unstyled { color: black; } .imo-overrides-table thead { background: #e9edc9; } .imo-overrides-table { width: 100%; background: rgb(254, 250, 224); } .imo-overrides-table td, .imo-overrides-table th { border: 1px solid #7b7b7b; } .imo-overrides-table tbody tr:hover { background: #dddddd; } .imo-default-module { background-color: #dddddd; } .imo-popup a:visited, .imo-popup a { color: rgb(42, 157, 143); } `); overrideShadowRoot.adoptedStyleSheets.push(shadowRootCss); const documentCss = new CSSStyleSheet(); documentCss.replaceSync(` .imo-module-dialog { border: white; } .imo-modal-container * { font-family: "DS Indigo", DSIndigo, Helvetica, Arial, sans-serif; } .imo-module-dialog.imo-overridden { } .imo-dialog-actions button { cursor: pointer; border-radius: 8px; border: 1px solid #666; background: #f2f2f2; color: #232323; height: 35px; } .imo-module-dialog table td:first-child { font-weight: bold; } .imo-module-dialog h3 { padding-bottom: 0.5rem; border-bottom: 1px solid #ddd; } .imo-dialog-actions { display: flex; align-items: center; justify-content: center; } `); document.adoptedStyleSheets.push(documentCss); overrideShadowRoot?.querySelector("button.imo-trigger")?.addEventListener("click", () => { if (overrideShadowRoot?.querySelector(".imo-header h1")) { overrideShadowRoot.querySelector(".imo-header h1").innerHTML = "1FE Widget Overrides"; overrideShadowRoot.querySelector(".imo-header p a").href = "https://github.docusignhq.com/pages/Core/1fe-docs/widgets/development/overrides/"; } copyImportMapOverridesButton("Copy overrides", () => { const copyLink = new URL(window.location.href); copyLink.searchParams.set( WIDGET_URL_OVERRIDES, JSON.stringify(window.importMapOverrides.getOverrideMap().imports) ); const textArea = document.createElement("textarea"); textArea.value = copyLink.toString(); document.body.appendChild(textArea); textArea.select(); document.execCommand("copy"); document.body.removeChild(textArea); }); if (!isEmpty(getParsedRuntimeConfigOverrides())) { copyImportMapOverridesButton( "Clear runtime config overrides", clearRuntimeConfigOverrides, // animate the button to glow on an interval (button) => { let colorIndex = 0; let animationInterval = null; if (!animationInterval) { const colors = ["red", "salmon"]; const animationSpeed = "0.75s"; button.style.transition = `background-color ${animationSpeed}`; animationInterval = window.setInterval(() => { button.style.backgroundColor = colors[colorIndex]; colorIndex = (colorIndex + 1) % colors.length; }, 750); } else if (animationInterval) { clearInterval(animationInterval); animationInterval = null; button.style.backgroundColor = "navajowhite"; } } ); } }); }; // src/shell/platformProps/utils/widgets/internal/system/get.ts var get = (_System, hostWidgetId) => async (requestedWidgetId, { variantId = getDefaultWidgetOptions().variantId } = getDefaultWidgetOptions()) => { const logger = getShellLogger(); const options = { variantId }; const widgetLogInfo = { hostWidget: { widgetId: hostWidgetId }, requestedWidget: { widgetId: requestedWidgetId, widgetPath: getWidgetPath(requestedWidgetId) } }; const getComponentFromModule2 = async (widgetId) => getComponentFromModule({ options, module: await _System.import(widgetId), log(message, otherData = {}) { logger.log({ ...otherData, message, category: "utils.widgets.get", widgetId: requestedWidgetId, isOverrideActive: isOverrideElementActive(), ...widgetLogInfo }); } }); if (!requestedWidgetId) { const errorMessage = "[platformProps.utils.widgets.get] No widget ID provided, please refer to API documentation: https://github.docusignhq.com/pages/Core/1fe-docs/widgets/utils/widgets/#get"; console.error(errorMessage); throw new Error(errorMessage); } if (!isSystemEnv()) { const errorMessage = "[platformProps.utils.widgets.get] Systemjs not detected. Something is critically wrong. Please reach out to 1FE team."; console.error(errorMessage); throw new Error(errorMessage); } try { const { requestedWidgetConfig, type } = getRequestedWidgetConfigWithoutRuntimeConfig({ hostWidgetId, requestedWidgetId, widgetConfigs: WIDGET_CONFIGS }); const isPinnedWidget = isWidgetTypePinned2(type); logPlatformUtilUsage({ utilNamespace: "widgets", functionName: "get", widgetId: hostWidgetId, args: { requestedWidgetId, isPinnedWidget, options }, attributes: { isSystemEnv } }); const importMapOverrides = window.importMapOverrides?.getOverrideMap() || {}; const doesPinnedWidgetOverrideExist = !!importMapOverrides.imports?.[requestedWidgetId]; if (!!requestedWidgetConfig && isPinnedWidget && !doesPinnedWidgetOverrideExist) { const requestedPinnedWidgetConfigBundleCdnUrl = getWidgetBundleCdnUrl({ widgetId: requestedWidgetId, version: requestedWidgetConfig.version }); return getComponentFromModule2( requestedPinnedWidgetConfigBundleCdnUrl.toString() ); } return getComponentFromModule2(requestedWidgetId); } catch (error) { const message = `[platformProps.utils.widgets.get] Getting widget by ID failed.`; console.error(message, error, widgetLogInfo); logger.error({ error, message, ...widgetLogInfo, category: "utils.widgets.get", isOverrideActive: isOverrideElementActive(), widgetId: requestedWidgetId }); throw new Error(message); } }; var getByUrl = (_System, hostWidgetId) => async (url, { variantId = getDefaultWidgetOptions().variantId } = getDefaultWidgetOptions()) => { const logger = getShellLogger(); const options = { variantId }; logPlatformUtilUsage({ utilNamespace: "widgets", functionName: "getByUrl", widgetId: hostWidgetId, args: { url, options }, attributes: { isSystemEnv } }); if (url && isSystemEnv()) { const urlAsAString = url.toString(); try { const module = await _System.import(urlAsAString); return getComponentFromModule({ options, module, log(message) { logger.log({ message, url: urlAsAString, category: "utils.widgets.getByUrl", widgetId: hostWidgetId }); } }); } catch (error) { const message = "[UTILS_API][WIDGETS] Getting widget by URL failed."; console.error(message, error, urlAsAString, options); logger.error({ error, message, url: urlAsAString, category: "utils.widgets.getByUrl", widgetId: hostWidgetId }); throw new Error(message); } } console.warn( "[UTILS_API] (widgets.getByUrl) Incorrect usage of API, please refer to API documentation." ); throw new Error("Improper usage of widgets.getByUrl() or bad environment."); }; var getAsset = (_System, hostWidgetId) => async (widgetId, path) => { const logger = getShellLogger(); logPlatformUtilUsage({ utilNamespace: "widgets", functionName: "getAsset", widgetId: hostWidgetId, args: { widgetId, path }, attributes: { isSystemEnv } }); if (widgetId && isSystemEnv()) { try { const oneDsBundleUrl = await (window.importMapOverrides ? window.importMapOverrides.getCurrentPageMap().then( (importMap) => importMap.imports[widgetId] ) : _System.resolve(widgetId)); const cdnBaseUrl = oneDsBundleUrl.replace(/js\/1fe-bundle\.js$/, ""); return await _System.import(`${cdnBaseUrl}${path}`); } catch (error) { const message = `[UTILS_API][WIDGETS] Getting widget asset by ID failed.`; console.error(message, error, widgetId, path); logger.error({ error, message, path, category: "utils.widgets.getAsset", widgetId }); throw new Error(message); } } console.warn( "[UTILS_API] (widgets.get) Incorrect usage of API, please refer to API documentation." ); throw new Error("Improper usage of widgets.getAsset() or bad environment."); }; // src/shell/platformProps/utils/widgets/internal/system/index.ts var widgets = ({ system, hostWidgetId }) => ({ get: get(system, hostWidgetId), getByUrl: getByUrl(system, hostWidgetId), getAsset: getAsset(system, hostWidgetId) }); // src/shell/platformProps/utils/widgets/internal/WidgetFrame/is-url.ts function isUrl(widgetRequest) { return widgetRequest instanceof URL; } // src/shell/platformProps/utils/widgets/internal/WidgetFrame/downloadWidget.ts async function downloadWidget(widgetId, setWidgetRenderStatus, options) { const { requestedWidgetConfigOrUrl, hostWidgetId, // hostProps, widgetOptions, widgetFrameId } = options; const widgetsInstance = widgets({ hostWidgetId, system: System }); const logger = getShellLogger(); const isWidgetOverriden = isOverrideElementActive(); const IS_PROD = ENVIRONMENT_CONFIG.isProduction; const widgetLoadTime = getShellPlatformUtils().appLoadTime; const logDownloadWidgetError = (message, error) => { logger.error({ message, parsedWidget: widgetId, error, widget: isUrl(widgetId) ? { widgetId: widgetId.toString(), version: "0.0.0" } : requestedWidgetConfigOrUrl, url: isUrl(widgetId) ? widgetId.toString() : void 0, isOverrideActive: isWidgetOverriden, widgetId: widgetId || widgetFrameId }); }; try { widgetLoadTime.markStart(widgetId.toString()); widgetLoadTime.markStart(`${widgetId}-download`); const getModule = isUrl(widgetId) ? () => widgetsInstance.getByUrl(widgetId, widgetOptions) : () => widgetsInstance.get(widgetId, widgetOptions); const module = await getModule(); widgetLoadTime.markEnd(`${widgetId}-download`, { detail: { status: "success" } }); logger.log({ message: `[1FE-Shell] Widget loaded`, widget: isUrl(widgetId) ? { widgetId: widgetId.toString(), version: "0.0.0" } : requestedWidgetConfigOrUrl, url: isUrl(widgetId) ? widgetId.toString() : void 0, isOverrideActive: isWidgetOverriden, widgetId: widgetId || widgetFrameId }); if (!isWidgetOverriden) { } if (module.default == null || typeof module.default !== "function") { throw new Error(`Widget ${widgetId} has no default export`); } setWidgetRenderStatus("rendered"); return module; } catch (error) { widgetLoadTime.markEnd(`${widgetId}-download`, { detail: { status: "failure" } }); widgetLoadTime.markEnd(widgetId.toString(), { detail: { status: "failure" } }); logDownloadWidgetError(`[1FE-Shell] Widget download failed`, error); setWidgetRenderStatus("error"); let errorMessage = `Failed to load widget: ${widgetId}`; if (!IS_PROD) { if (error instanceof Error) { errorMessage += ` ErrorMessage: ${error.message}, ErrorStack: ${error.stack}`; } else { errorMessage += ` ErrorMessage: ${String(error)}`; } } throw new Error(errorMessage); } } // src/shell/constants/platform-props.ts var ONEDS_SHELL_IMPORT_MAP_ID = "app:@1fe/shell"; // src/shell/platformProps/context/getHostWidget/index.ts var getHostWidget = (widget) => { const immediateHostWidgetNodeId = widgetDependencyTree.getByKey(widget.widgetId)?.parents.keys().next().value; if (!immediateHostWidgetNodeId) { return null; } const node = widgetDependencyTree.getById(immediateHostWidgetNodeId); if (!node) { return null; } return { id: node.key, data: node.data }; }; // src/shell/platformProps/context/index.ts var getContext = (widget, options = getDefaultWidgetOptions()) => { return { self: { widgetId: widget.widgetId, version: widget.version, variantId: options.variantId }, getHost: () => getHostWidget(widget) // The following methods are intentionally disabled for now // context: https://docusign.slack.com/archives/C04CZ0F69CJ/p1709672412983809 // getTree, // yggdrasil: getTree, // getPlugin, }; }; // src/shell/platformProps/index.ts var getPlatformProps = (widget, options = getDefaultWidgetOptions()) => { const platformProps2 = { environment: ENVIRONMENT_CONFIG.environment, context: getContext(widget, options), utils: getPlatformUtils(widget) }; return deepFreeze(platformProps2); }; // src/shell/utils/widget-url-overrides.ts var getParsedWidgetUrlOverrides = () => { try { const param = new URL(window.location.href).searchParams.get( WIDGET_URL_OVERRIDES ); return param ? JSON.parse(param) : {}; } catch (e) { console.error( `[1FE][RUNTIME_CONFIG_OVERRIDES] Unable to parse JSON in ${WIDGET_URL_OVERRIDES} param` ); return {}; } }; var isAllowedSource = (source, allowedSources) => { try { const sourceUrl = new URL(source); return allowedSources.includes(sourceUrl.hostname); } catch { } return false; }; // src/shell/utils/system-helpers.ts var CONTEXT_IMPORT_NAME = "1feContext"; var INTERNAL_CONTEXT_REF = Symbol("1feContext"); var hackyWidgetIdDetermination = (url) => { if (!isAllowedSource( url, DYNAMIC_CONFIGS?.devtools?.importMapOverrides.allowedSources )) { return; } const template2 = /1ds\/widgets\/(.*?)\/js\/1ds-bundle.js/i; const match = url.match(template2); if (!match || match.length < 2) { return; } const mayBeWidgetName = match[1]; const parts = mayBeWidgetName.split("/"); if (parts.length < 3) { return; } const [org, widgetName, version] = parts; const widgetId = `${org}/${widgetName}`; const mayBeWidget = WIDGET_CONFIGS.get(widgetId); if (!mayBeWidget) { return; } console.log( `[1DS-Context] Best case effort for pinned widget:${widgetId}, version:${version} from url ${url}` ); return { ...mayBeWidget, version }; }; var addScopedImportMapForPlatformProps = (widgetId, platformProps2) => { const scopedPlatformPropsIdentifier = `${ONEDS_SHELL_IMPORT_MAP_ID}::${widgetId}`; System.set(scopedPlatformPropsIdentifier, { __esModule: true, platformProps: platformProps2 }); }; var injectScopedWidgetContext = (requestedBy) => { let platformProps2; const existingContext = Object.getOwnPropertyDescriptor( requestedBy, INTERNAL_CONTEXT_REF ); if (existingContext?.value) { platformProps2 = existingContext.value; } else { platformProps2 = getPlatformProps(requestedBy.data); Object.defineProperty(requestedBy, INTERNAL_CONTEXT_REF, { value: platformProps2 }); } const scopeId = `oneds://${requestedBy.data.widgetId}/${requestedBy.id}`; System.set(scopeId, { __esModule: true, platformProps: platformProps2 }); return scopeId; }; var tryGetParentTreeNode = (url) => { if (!url) { return; } const requestedBy = widgetContextDependencyTree.getByKey(url); if (!requestedBy) { const logger = getShellLogger(); logger.error({ message: "[1DS-Injection] Couldn't find parent widget for url", url }); return; } return requestedBy; }; var patchSystemJSResolve = () => { const SYSTEMJS_PROTOTYPE = System.constructor.prototype; const existingHook = SYSTEMJS_PROTOTYPE.resolve; SYSTEMJS_PROTOTYPE.resolve = function oneDsResolver(importName, requestedByUrl) { const requestedBy = tryGetParentTreeNode(requestedByUrl); if (importName === CONTEXT_IMPORT_NAME) { if (!requestedBy) { return; } const scopeId = injectScopedWidgetContext(requestedBy); return scopeId; } const resolvedUrl = existingHook.call( this, importName, requestedByUrl ); const mayBeExistingNode = widgetContextDependencyTree.getByKey(resolvedUrl); if (!mayBeExistingNode) { let mayBeWidget = WIDGET_CONFIGS.get(importName); if (!mayBeWidget) { mayBeWidget = hackyWidgetIdDetermination(importName); } if (!mayBeWidget) { console.log( `[1DS-Context] Couldn't find configuration for url ${resolvedUrl}. Treating as a library.` ); mayBeWidget = { widgetId: importName, // _url: resolvedUrl, version: "latest", // activePhasedDeployment: false, runtime: {} }; } widgetContextDependencyTree.add( resolvedUrl, mayBeWidget, "library", // This is incorrect since everything is being treated as a library. requestedBy ); } return resolvedUrl; }; return () => { SYSTEMJS_PROTOTYPE.resolve = existingHook; }; }; var platformProps = {}; function WidgetLoader({ widgetFrameId, requestedWidgetConfigOrUrl, widgetProps, setWidgetRenderStatus, options: { variantId, Loader }, // dragon: always deconstruct options to maintain stability in useMemo calls below hostWidgetId }) { const { widgetId, version } = requestedWidgetConfigOrUrl; const platformProps2 = useMemo( () => getPlatformProps(requestedWidgetConfigOrUrl, { variantId}), [`${widgetId}@${version}`, variantId] ); const { children, ...hostProps } = widgetProps; const LazyWidget = useMemo(() => { return React__default.lazy( () => downloadWidget(widgetId, setWidgetRenderStatus, { widgetFrameId, requestedWidgetConfigOrUrl, hostWidgetId, widgetOptions: { variantId, Loader } }) ); }, [`${widgetId}@${version}`, variantId]); addScopedImportMapForPlatformProps(widgetId, platformProps2); const widget = /* @__PURE__ */ jsx(LazyWidget, { platform: platformProps2, host: hostProps, children }); return /* @__PURE__ */ jsx( Suspense, { fallback: Loader, children: widget }, `${widgetFrameId}-suspense` ); } function WidgetURLLoader({ widgetFrameId, requestedWidgetConfigOrUrl, setWidgetRenderStatus, widgetProps = {}, hostWidgetId, options: { variantId } // dragon: always deconstruct options to maintain stability in useMemo calls below }) { const Loader = getDefaultWidgetOptions().Loader; const platformProps2 = useMemo( () => getPlatformProps( { widgetId: requestedWidgetConfigOrUrl.toString(), version: "0.0.0", runtime: {} }, { variantId} ), [requestedWidgetConfigOrUrl, variantId] ); addScopedImportMapForPlatformProps("test-widget", platformProps2); const { children, ...hostProps } = widgetProps; const LazyWidget = useMemo(() => { return React__default.lazy( () => downloadWidget( requestedWidgetConfigOrUrl, setWidgetRenderStatus, { widgetFrameId, requestedWidgetConfigOrUrl, hostWidgetId, widgetOptions: { variantId, Loader } } ) ); }, [requestedWidgetConfigOrUrl, variantId]); return /* @__PURE__ */ jsx( Suspense, { fallback: Loader, children: /* @__PURE__ */ jsx(LazyWidget, { platform: platformProps2, host: hostProps, children }) }, `${requestedWidgetConfigOrUrl.toString()}-suspense` ); } function WidgetErrorBoundary({ widgetFrameId, requestedWidgetConfigOrUrl, hostWidgetId, fallback, children }) { const renderStartTime = useRef(Date.now()); const isActiveOverride = isOverrideElementActive(); const logger = getShellLogger(); const isUrl2 = requestedWidgetConfigOrUrl instanceof URL; const { type = void 0 } = isUrl2 ? {} : getRequestedWidgetConfigWithoutRuntimeConfig({ hostWidgetId, requestedWidgetId: requestedWidgetConfigOrUrl.widgetId, widgetConfigs: WIDGET_CONFIGS }); const handleRender = useCallback(() => { const isWidgetOverriden = isOverrideElementActive(); renderStartTime.current = Date.now(); logger.log({ mes