{"version":3,"file":"moin-loader.js","sources":["../src/appconfig.ts","../src/logger.ts","../node_modules/nanoid/index.browser.js","../src/rpc/index.js","../loader/iframe.ts","../loader/loader-styles.js","../loader/helper.js","../loader/moin-loader.js"],"sourcesContent":["\n// @ts-expect-error we don't really know if the config is there or not\nconst createConfig = (env) => {\n const config = {\n environment: env?.MODE,\n isDevelopment: env.DEV,\n isProduction: env.PROD,\n apiPath: env.VITE_APP_API_PATH,\n botPath: env.VITE_APP_BOT_PATH,\n appVersion: env.VITE_APP_VERSION,\n useSRI: env.VITE_APP_USE_SRI === 'true' || false,\n sri: {\n SRI_PRIMUS: env.VITE_SRI_PRIMUS,\n SRI_CSS: env.VITE_SRI_CSS,\n SRI_MAIN: env.VITE_SRI_MAIN,\n SRI_FONT_500: env.VITE_SRI_FONT_500,\n SRI_FONT_REG: env.VITE_SRI_FONT_REG,\n },\n };\n if (!config.apiPath) {\n throw new Error(`env variable is missing: VITE_APP_API_PATH`);\n }\n if (!config.botPath) {\n throw new Error(`env variable is missing: VITE_APP_BOT_PATH`);\n }\n\n return config;\n};\n\n\nconst config = createConfig(import.meta.env || process.env);\n\nexport default config;\n","/* eslint-disable prefer-rest-params */\nimport config from './appconfig';\nconst isProduction = config.isProduction;\n\nconst isObject = (obj: object) =>\n Object.prototype.toString.call(obj) === '[object Object]';\n\nconst testSessionStorageAvailable = function () {\n let guard = true;\n try {\n guard = window ? true : false;\n guard = window.sessionStorage ? true : false;\n // @ts-expect-error guard is always true, but not in webview context\n guard = window.sessionStorage.getItem ? true : false;\n } catch {\n // we crash on ios / webview don't use LS\n console.error('crash without window / sessionStorage');\n guard = false;\n }\n return guard;\n};\n\nconst isSessionStorageAvailable = testSessionStorageAvailable();\n\nlet isDebugMode = false;\nif (isSessionStorageAvailable) {\n isDebugMode = window?.sessionStorage?.getItem('moindebug') === 'true';\n}\n\nconst loggerFunctions = {\n log: window.console.log.bind(window.console, '\\x1b[1mlog\\x1b[0m: %s'),\n warn: window.console.warn.bind(window.console, '\\x1b[33mwar\\x1b[0m: %s'),\n error: window.console.error.bind(window.console, '\\x1b[31merr\\x1b[0m: %s'),\n};\n\nconst logg = window.console.log.bind(window.console, '%s');\nconst logLine = function () {\n const level = arguments[0];\n let msg = '';\n for (let i = 1; i < arguments.length; i++) {\n const isObj = isObject(arguments[i]) || Array.isArray(arguments[i]);\n msg += ` ${isObj ? JSON.stringify(arguments[i]) : arguments[i]}`;\n }\n const now = new Date().toLocaleTimeString('de-de');\n const logMessage = `${now} | ${level} | ${msg}`;\n logg(logMessage);\n\n};\n\nif (!isDebugMode && isProduction) {\n loggerFunctions.log = () => { };\n loggerFunctions.warn = () => { };\n loggerFunctions.error = () => { };\n}\n\nif (isDebugMode) {\n\n loggerFunctions.log = logLine.bind(console.log);\n loggerFunctions.warn = logLine.bind(console.warn);\n loggerFunctions.error = logLine.bind(console.error);\n}\n\nloggerFunctions.log(\n 'log',\n `logger isProduction: ${isProduction}, isDebugMode ${isDebugMode}`,\n);\n\nexport default {\n ...loggerFunctions,\n};\n","export { urlAlphabet } from './url-alphabet/index.js'\nexport let random = bytes => crypto.getRandomValues(new Uint8Array(bytes))\nexport let customRandom = (alphabet, defaultSize, getRandom) => {\n let mask = (2 << (Math.log(alphabet.length - 1) / Math.LN2)) - 1\n let step = -~((1.6 * mask * defaultSize) / alphabet.length)\n return (size = defaultSize) => {\n let id = ''\n while (true) {\n let bytes = getRandom(step)\n let j = step\n while (j--) {\n id += alphabet[bytes[j] & mask] || ''\n if (id.length === size) return id\n }\n }\n }\n}\nexport let customAlphabet = (alphabet, size = 21) =>\n customRandom(alphabet, size, random)\nexport let nanoid = (size = 21) =>\n crypto.getRandomValues(new Uint8Array(size)).reduce((id, byte) => {\n byte &= 63\n if (byte < 36) {\n id += byte.toString(36)\n } else if (byte < 62) {\n id += (byte - 26).toString(36).toUpperCase()\n } else if (byte > 62) {\n id += '-'\n } else {\n id += '_'\n }\n return id\n }, '')\n","import { nanoid } from 'nanoid';\nwindow.__moinrpc = {\n // map for holding the exposed functions\n exposedFunctions: new Map(),\n targets: new Map(),\n};\n\nexport function expose(name, func) {\n if (window.__moinrpc.exposedFunctions.has(name)) {\n console.error('function with this name already has been exposed');\n return;\n }\n window.__moinrpc.exposedFunctions.set(name, func);\n}\n\n/**\n * \n * @param {object} options\n * @param {Window} options.target\n * @param {string} [options.targetOrigin='*'] options.targetOrigin\n * @param {string} [options.service='moin-rpc'] options.service\n * @returns \n */\nexport function getTarget(\n options = { target: null, targetOrigin: '*', service: 'moin-rpc' },\n) {\n let target = options.target;\n if (!target) throw new Error('target must be set');\n // @ts-expect-error some weirdness with the types\n if (window.__moinrpc.targets.has(target.postMessage)) {\n // @ts-expect-error some weirdness with the types\n return window.__moinrpc.targets.get(target.postMessage);\n } else {\n const channel = createRPCChannel(options);\n // @ts-expect-error some weirdness with the types\n window.__moinrpc.targets.set(target.postMessage, channel);\n return channel;\n }\n}\n/**\n * \n * @param {object} options\n * @param {Window} options.target\n * @param {string} [options.targetOrigin='*'] options.targetOrigin\n * @param {string} [options.service='moin-rpc'] options.service\n * @returns \n */\nconst createRPCChannel = (\n options = { target: null, targetOrigin: '*', service: 'moin-rpc' },\n) => {\n // map for holding the \"reponse\" promises\n const responseKeys = new Map();\n // we scoping the createChannel() function to the instance\n // otherwise serveral instances would interfer on the generic window message event\n const instanceId = nanoid();\n // target we want send messages to\n // this needs to be set before we can send messages\n // it also need to have a postMessage function\n let target = options.target;\n let targetOrigin = options.targetOrigin || '*';\n // identifier for the service that we don't use wrong postmessage events\n let service = options.service || 'moin-rpc';\n\n const createResponseMessage = (id, result, remoteInstanceId) => ({\n instanceId: remoteInstanceId,\n service,\n type: 'response',\n result,\n id,\n });\n\n const createErrorMessage = (id, error, remoteInstanceId) => ({\n instanceId: remoteInstanceId,\n service,\n type: 'error',\n result: error,\n id,\n });\n\n const createFunctionCallMessage = (func, args, id, instance) => ({\n instanceId: instance,\n service,\n type: 'call',\n func,\n id,\n args: JSON.stringify(args),\n });\n\n // Handler for the case we got a remote function call\n // if we have to function exposed we call the function and funnnel it through a promise resolve\n // in case the function is async, if not the result will go through the promise resolve\n // if the the function was not exposed we return an error via postMessage\n // we directly anser to the frame the message orginated from\n const handleFunctionCall = (event) => {\n const { func, args, id, instanceId } = event.data;\n const answerTarget = event.source;\n const funcToCall = window.__moinrpc.exposedFunctions.get(func);\n // function was not found so we return an error via postMessage\n if (!funcToCall) {\n answerTarget.postMessage(\n createErrorMessage(id, new Error('function not found'), instanceId),\n targetOrigin,\n );\n return;\n }\n // we call the function and return the result via postMessage\n const result = funcToCall.call(null, JSON.parse(args));\n Promise.resolve(result)\n .then((resultValue) => {\n answerTarget.postMessage(\n createResponseMessage(id, resultValue),\n targetOrigin,\n );\n })\n .catch((e) => {\n // error in the desired function call we pass on the error via postMessage\n answerTarget.postMessage(createErrorMessage(id, e), targetOrigin);\n });\n };\n\n // when the other frame answers to our call we resolve the promise in a success case\n // we also delete the promise from the map\n const handleResponse = (id, result) => {\n responseKeys.get(id).onSuccess(result);\n responseKeys.delete(id);\n };\n\n // when the function call produced an error or was not available we reject the promise and delete it from the map\n const handleError = (id, result) => {\n responseKeys.get(id).onError(result);\n responseKeys.delete(id);\n };\n\n // global handler for all messages\n const handleMessage = (event) => {\n // make sure we only consume message addressed to us\n if (!event.data) return;\n if (event.data.service !== service) return;\n const { type, id, result } = event.data;\n switch (type) {\n case 'call':\n handleFunctionCall(event);\n break;\n case 'response':\n handleResponse(id, result);\n break;\n case 'error':\n handleError(id, result);\n break;\n default:\n // this should never happen\n console.error('rpc unknown message type', event.data);\n break;\n }\n };\n window.addEventListener('message', handleMessage);\n\n return {\n call: (name, args = {}) => {\n // we might want opts here to override a target and targetOrigin\n\n // TODO check if args are valid json\n const readyState = target?.readyState || target?.document?.readyState;\n const isReady = readyState === 'complete' || readyState === 'interactive';\n return new Promise((resolve, reject) => {\n if (readyState === 'interactive') {\n console.warn('iframe is not fully loaded');\n }\n if (!isReady) reject(new Error(`target not ready`));\n if (!target) reject(new Error(`cant call ${name}, target set`));\n try {\n JSON.stringify(args);\n } catch {\n reject(new Error(`cant call ${name}, args not valid`));\n }\n // create handler for response and save with the corresponding id for handling the answer\n const onSuccess = (result) => resolve(result);\n const onError = (error) => reject(error);\n const id = nanoid();\n responseKeys.set(id, { name, onSuccess, onError });\n target.postMessage(\n createFunctionCallMessage(name, args, id, instanceId),\n targetOrigin,\n );\n });\n },\n\n /* remove: (name) => {\n exposedFunctions.delete(name);\n\n // TODO get open functions calls and error them\n // otherwise they will never resolve and are a potential memory leak\n // this break my brain atm, so I'll leave it for later me =) \n // Have fun knobhead \n for (let item of responseKeys.entires()) {\n console.log(item);\n if (item[1].name === name) {\n const openCall = responseKeys.get(item[0]);\n openCall.onError(new Error(\"function removed\"));\n responseKeys.delete(item[0]);\n }\n } \n },*/\n closeChannel: () => {\n responseKeys.clear();\n window.__moinrpc.exposedFunctions.clear();\n window.removeEventListener('message', handleMessage);\n },\n };\n};\n","import { CSSProperties } from \"vue\";\n\nexport const iframeResetStyles = {\n animation: 'none',\n animationName: 'none',\n backfaceVisibility: 'visible',\n background: 'transparent',\n backgroundAttachment: 'scroll',\n border: '0',\n borderStyle: 'none',\n borderWidth: 'medium',\n borderColor: 'inherit',\n boxShadow: 'none',\n boxSizing: 'content-box',\n color: 'inherit',\n cursor: 'auto',\n display: 'inline',\n height: 'auto',\n left: 'auto',\n margin: '0',\n maxWidth: 'none',\n maxHeight: '100dvh',\n minWidth: 'unset',\n minHeight: 'unset',\n opacity: 1,\n outline: '0',\n outlineColor: 'invert',\n outlineStyle: 'none',\n outlineWidth: 'medium',\n overflow: 'visible',\n overflowX: 'visible',\n overflowY: 'visible',\n padding: '0',\n perspective: 'none',\n position: 'static',\n top: 'auto',\n transform: 'none',\n transition: 'none',\n visibility: 'visible',\n whiteSpace: 'normal',\n widows: 0,\n width: 'auto',\n zIndex: 'auto',\n};\n\n// we start with a frame of 0x0px\n// the onmount will resize the frame to the content\nconst initialFrameStyles = {\n position: 'absolute',\n bottom: '0px',\n right: '0px',\n width: '0px',\n height: '0px',\n overflow: 'hidden'\n}\n\n/**\n *\n * @param {object} style\n * @returns {string}\n */\n\nfunction toCssString(style) {\n return Object.keys(style).reduce((acc, key) => (\n acc + key.split(/(?=[A-Z])/).join('-').toLowerCase() + ':' + style[key] + ';'\n ), '');\n}\n\nfunction createCSSLinkElement(options) {\n /* const { id, sri, href } = options;\n const idAttr = `id=\"${id}\"`\n const sriPart = `integrity=\"${sri}\" crossorigin=\"anonymous\"`\n return `` */\n const { id, sri, href } = options;\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = href\n\n if (id) {\n link.id = id\n }\n if (sri) {\n link.integrity = sri\n link.crossOrigin = 'anonymous'\n }\n\n return link\n}\n\nfunction createScriptTagElement(options) {\n /* const { id, src, sri, type } = options;\n const idAttr = `id=\"${id}\"`\n const typeAttr = `type=\"${type}\"`\n const sriAttr = `integrity=\"${sri}\" crossorigin=\"anonymous\"`\n return `` */\n const { id, src, sri, type } = options;\n const scriptTag = document.createElement('script')\n scriptTag.src = src\n if (id) {\n scriptTag.id = id\n }\n if (sri) {\n scriptTag.integrity = sri\n scriptTag.crossOrigin = 'anonymous'\n }\n if (type) {\n scriptTag.type = type\n }\n return scriptTag\n}\n\nfunction buildIframe(iframe, cssFiles, scriptFiles, bodyStyles, attributes) {\n const innercontainer = document.createElement('div')\n innercontainer.id = 'moinai-container'\n innercontainer.style.height = '100%'\n innercontainer.style.width = '100%'\n\n /* console.log('---> ', iframe, cssFiles, scriptFiles, bodyStyles, attributes, iframe.contentDocument) */\n iframe.contentDocument.body.appendChild(innercontainer)\n iframe.contentDocument.height = '100%'\n\n const bStyles = `${bodyStyles.map((item) => toCssString(item)).join(' ')}`\n iframe.contentDocument.body.style = bStyles\n\n attributes.forEach((attr) => {\n iframe.contentDocument.body.setAttribute(attr.attrName, attr.value)\n })\n\n cssFiles.forEach((css) => {\n const link = createCSSLinkElement(css)\n iframe.contentDocument.head.appendChild(link)\n })\n\n scriptFiles.forEach((script) => {\n const scriptTag = createScriptTagElement(script)\n iframe.contentDocument.body.appendChild(scriptTag)\n /* iframe.contentDocument.head.appendChild(scriptTag) */\n })\n}\n\n\nfunction createIframeElement(container: HTMLElement, id: string, cssFiles: CSSLink[], scriptFiles: Script[], innerBodyStyles: CSSProperties[], iframeStyles: CSSProperties[], attributes: Attributes[], title: string) {\n const iframe = document.createElement('iframe');\n iframe.id = id;\n iframe.title = title;\n iframe.classList.add(id);\n container.appendChild(iframe);\n const newStyles = Object.assign({}, iframeResetStyles, initialFrameStyles, ...iframeStyles)\n Object.assign(iframe.style, newStyles)\n return new Promise(async (resolve) => {\n setTimeout(async () => {\n /* iframe.srcdoc = buildSrcdocString(cssFiles, scriptFiles, innerBodyStyles, attributes) */\n await buildIframe(iframe, cssFiles, scriptFiles, innerBodyStyles, attributes)\n /* console.log('iframe created', iframe.contentDocument) */\n resolve(iframe)\n }, 300);\n\n })\n}\n\n\ntype CSSLink = {\n id: string,\n href: string,\n sri?: string\n}\n\ntype Script = {\n id: string,\n src: string,\n sri?: string,\n type?: string\n}\n\ntype Attributes = {\n attrName: string,\n value: string\n}\n\nexport class IframeBuilder {\n private _id: string;\n private _iframetitle: string;\n private _css: CSSLink[];\n private _scripts: Script[];\n private _frameStyles: CSSProperties[];\n private _innerBodyStyles: CSSProperties[];\n private _attributes: Attributes[];\n\n constructor(id: string, options = {\n title: 'moinai-iframe'\n }) {\n const title = options.title\n this._id = id\n // accessability title to the iframe\n this._iframetitle = title\n this._css = []\n this._scripts = []\n this._frameStyles = []\n this._attributes = []\n this._innerBodyStyles = []\n }\n\n /**\n *\n * adds an attribute to the inner body tag of the iframe element\n * @example\n * .addInnerAttribute('data-example', 'example-value')\n * will add the attribute to the body element\n *
\n */\n addInnerAttribute(attrName: string, value: string) {\n this._attributes.push({\n attrName,\n value\n })\n return this\n }\n\n /**\n * adds an html Link element to the iframe head\n */\n addCSS(css: CSSLink) {\n this._css.push(css)\n return this;\n }\n\n /**\n * adds an html Link element to the iframe head\n */\n addScript(script: Script) {\n this._scripts.push(script)\n return this;\n }\n\n /**\n * Add styles to the iframe element\n *\n * @example\n * .addIFrameStyles({\n * position: 'absolute',\n * bottom: '0px',\n * right: '0px',\n * width: '100vw',\n * height: '100vh',\n * zIndex: '100000',\n * overflow: 'hidden'\n * })\n *\n * creates following iframe tag\n *