From c8d7edfeab656c0d699d132549b4cd8c3e9daab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20=C5=BBegle=C5=84?= Date: Mon, 21 Jun 2021 12:55:47 +0200 Subject: [PATCH] Fix app configuration frame embedding (#1172) * Fix app configuration frame embedding * Tidy up code --- .../AppDetailsSettingsPage.tsx | 47 ++---------- .../useAppConfigLoader.ts | 75 +++++++++++++++++++ 2 files changed, 83 insertions(+), 39 deletions(-) create mode 100644 src/apps/components/AppDetailsSettingsPage/useAppConfigLoader.ts diff --git a/src/apps/components/AppDetailsSettingsPage/AppDetailsSettingsPage.tsx b/src/apps/components/AppDetailsSettingsPage/AppDetailsSettingsPage.tsx index 2ccf98d73c2..76207cfed6e 100644 --- a/src/apps/components/AppDetailsSettingsPage/AppDetailsSettingsPage.tsx +++ b/src/apps/components/AppDetailsSettingsPage/AppDetailsSettingsPage.tsx @@ -7,12 +7,12 @@ import Hr from "@saleor/components/Hr"; import useTheme from "@saleor/hooks/useTheme"; import { sectionNames } from "@saleor/intl"; import classNames from "classnames"; -import React, { useEffect, useRef } from "react"; +import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; -import urlJoin from "url-join"; import { App_app } from "../../types/App"; import { useStyles } from "./styles"; +import useAppConfigLoader from "./useAppConfigLoader"; import useSettingsBreadcrumbs from "./useSettingsBreadcrumbs"; export interface AppDetailsSettingsPageProps { @@ -30,45 +30,14 @@ export const AppDetailsSettingsPage: React.FC = ({ onBack, onError }) => { - const iframeRef = useRef(null); const intl = useIntl(); const classes = useStyles({}); - const { sendThemeToExtension } = useTheme(); const [breadcrumbs, onBreadcrumbClick] = useSettingsBreadcrumbs(); - - useEffect(() => { - if (!iframeRef.current?.innerHTML && data?.configurationUrl) { - fetch(data?.configurationUrl, { - headers: { - "x-saleor-domain": backendHost, - "x-saleor-token": data.accessToken - }, - method: "GET" - }) - .then(async response => { - const url = new URL(response.url); - const text = await response.text(); - const content = new DOMParser().parseFromString(text, "text/html"); - - const iFrame = document.createElement("iframe"); - iFrame.src = "about:blank"; - iFrame.id = "extension-app"; - iframeRef.current.innerHTML = ""; - iframeRef.current.appendChild(iFrame); - const iFrameDoc = - iFrame.contentWindow && iFrame.contentWindow.document; - - const documentElement = content.documentElement; - const formScript = documentElement.querySelector("script"); - const formURL = new URL(documentElement.querySelector("script").src); - formScript.src = `${urlJoin(url.origin, formURL.pathname)}`; - iFrameDoc.write(content.documentElement.innerHTML); - iFrameDoc.close(); - iFrame.contentWindow.onload = sendThemeToExtension; - }) - .catch(() => onError()); - } - }, [data]); + const { sendThemeToExtension } = useTheme(); + const frameContainer = useAppConfigLoader(data, backendHost, { + onError, + onLoad: sendThemeToExtension + }); return ( @@ -135,7 +104,7 @@ export const AppDetailsSettingsPage: React.FC = ({
-
+
); diff --git a/src/apps/components/AppDetailsSettingsPage/useAppConfigLoader.ts b/src/apps/components/AppDetailsSettingsPage/useAppConfigLoader.ts new file mode 100644 index 00000000000..c73221d7c68 --- /dev/null +++ b/src/apps/components/AppDetailsSettingsPage/useAppConfigLoader.ts @@ -0,0 +1,75 @@ +import { AppFragment } from "@saleor/fragments/types/AppFragment"; +import { useEffect, useRef } from "react"; +import urlJoin from "url-join"; + +export type UseAppConfigLoaderCallbacks = Record< + "onLoad" | "onError", + () => void +>; + +function fixRelativeScriptSrc(origin: string) { + return (node: HTMLScriptElement) => { + // Using node.getAttribute beacuse node.src returns absolute path + const src = node.getAttribute("src"); + if (src?.startsWith("/")) { + node.src = urlJoin(origin, src); + } + }; +} + +async function fetchAndSetContent( + frameContainer: HTMLDivElement, + data: AppFragment, + backendHostname: string, + { onError, onLoad }: UseAppConfigLoaderCallbacks +) { + if (!frameContainer?.innerHTML && data?.configurationUrl) { + try { + const response = await fetch(data?.configurationUrl, { + headers: { + "x-saleor-domain": backendHostname, + "x-saleor-token": data.accessToken + }, + method: "GET" + }); + + const url = new URL(response.url); + const text = await response.text(); + const content = new DOMParser().parseFromString(text, "text/html"); + + const frame = document.createElement("iframe"); + frame.src = "about:blank"; + frame.id = "extension-app"; + frameContainer.innerHTML = ""; + frameContainer.appendChild(frame); + const frameContent = frame.contentWindow.document; + + const documentElement = content.documentElement; + const scriptNodes = documentElement.querySelectorAll("script"); + + scriptNodes.forEach(fixRelativeScriptSrc(url.origin)); + frameContent.write(content.documentElement.innerHTML); + frameContent.close(); + frame.contentWindow.onload = onLoad; + } catch (error) { + console.error(error); + onError(); + } + } +} + +function useAppConfigLoader( + data: AppFragment, + backendHost: string, + callbacks: UseAppConfigLoaderCallbacks +) { + const frameContainer = useRef(null); + + useEffect(() => { + fetchAndSetContent(frameContainer.current, data, backendHost, callbacks); + }, [data]); + + return frameContainer; +} + +export default useAppConfigLoader;