Fix app configuration frame embedding (#1172)

* Fix app configuration frame embedding

* Tidy up code
This commit is contained in:
Dominik Żegleń 2021-06-21 12:55:47 +02:00 committed by GitHub
parent 4056ac395b
commit c8d7edfeab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 83 additions and 39 deletions

View file

@ -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<AppDetailsSettingsPageProps> = ({
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 (
<Container>
@ -135,7 +104,7 @@ export const AppDetailsSettingsPage: React.FC<AppDetailsSettingsPageProps> = ({
<Hr />
<CardSpacer />
<div ref={iframeRef} className={classes.iframeContainer} />
<div ref={frameContainer} className={classes.iframeContainer} />
<CardSpacer />
</Container>
);

View file

@ -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<HTMLDivElement>(null);
useEffect(() => {
fetchAndSetContent(frameContainer.current, data, backendHost, callbacks);
}, [data]);
return frameContainer;
}
export default useAppConfigLoader;