Fix app configuration frame embedding (#1172)
* Fix app configuration frame embedding * Tidy up code
This commit is contained in:
parent
4056ac395b
commit
c8d7edfeab
2 changed files with 83 additions and 39 deletions
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
Loading…
Reference in a new issue