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 useTheme from "@saleor/hooks/useTheme";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import React, { useEffect, useRef } from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import urlJoin from "url-join";
|
|
||||||
|
|
||||||
import { App_app } from "../../types/App";
|
import { App_app } from "../../types/App";
|
||||||
import { useStyles } from "./styles";
|
import { useStyles } from "./styles";
|
||||||
|
import useAppConfigLoader from "./useAppConfigLoader";
|
||||||
import useSettingsBreadcrumbs from "./useSettingsBreadcrumbs";
|
import useSettingsBreadcrumbs from "./useSettingsBreadcrumbs";
|
||||||
|
|
||||||
export interface AppDetailsSettingsPageProps {
|
export interface AppDetailsSettingsPageProps {
|
||||||
|
@ -30,45 +30,14 @@ export const AppDetailsSettingsPage: React.FC<AppDetailsSettingsPageProps> = ({
|
||||||
onBack,
|
onBack,
|
||||||
onError
|
onError
|
||||||
}) => {
|
}) => {
|
||||||
const iframeRef = useRef(null);
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const classes = useStyles({});
|
const classes = useStyles({});
|
||||||
const { sendThemeToExtension } = useTheme();
|
|
||||||
const [breadcrumbs, onBreadcrumbClick] = useSettingsBreadcrumbs();
|
const [breadcrumbs, onBreadcrumbClick] = useSettingsBreadcrumbs();
|
||||||
|
const { sendThemeToExtension } = useTheme();
|
||||||
useEffect(() => {
|
const frameContainer = useAppConfigLoader(data, backendHost, {
|
||||||
if (!iframeRef.current?.innerHTML && data?.configurationUrl) {
|
onError,
|
||||||
fetch(data?.configurationUrl, {
|
onLoad: sendThemeToExtension
|
||||||
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]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
@ -135,7 +104,7 @@ export const AppDetailsSettingsPage: React.FC<AppDetailsSettingsPageProps> = ({
|
||||||
<Hr />
|
<Hr />
|
||||||
|
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<div ref={iframeRef} className={classes.iframeContainer} />
|
<div ref={frameContainer} className={classes.iframeContainer} />
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
</Container>
|
</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