[FRONT COMPONENTS] Retrieve the front components from the backend (#17813)
- Pass the auth token to worker - Fetch the file from the rest API with the auth token - Create the blob url - Update the stories to pass a fake token which will be ignored --------- Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
This commit is contained in:
+10
-4
@@ -1,4 +1,5 @@
|
||||
import { getMockFrontComponentUrl } from '@/front-components/utils/mockFrontComponent';
|
||||
import { REST_API_BASE_URL } from '@/apollo/constant/rest-api-base-url';
|
||||
import { getTokenPair } from '@/apollo/utils/getTokenPair';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
@@ -13,7 +14,7 @@ type FrontComponentRendererProps = {
|
||||
};
|
||||
|
||||
export const FrontComponentRenderer = ({
|
||||
frontComponentId: _frontComponentId,
|
||||
frontComponentId,
|
||||
}: FrontComponentRendererProps) => {
|
||||
const theme = useTheme();
|
||||
const [hasError, setHasError] = useState(false);
|
||||
@@ -22,6 +23,9 @@ export const FrontComponentRenderer = ({
|
||||
const { executionContext, frontComponentHostCommunicationApi } =
|
||||
useFrontComponentExecutionContext();
|
||||
|
||||
const componentUrl = `${REST_API_BASE_URL}/front-components/${frontComponentId}`;
|
||||
const authToken = getTokenPair()?.accessOrWorkspaceAgnosticToken?.token;
|
||||
|
||||
const handleError = (error?: Error) => {
|
||||
if (isDefined(error)) {
|
||||
const errorMessage = error.message;
|
||||
@@ -33,14 +37,16 @@ export const FrontComponentRenderer = ({
|
||||
setHasError(true);
|
||||
};
|
||||
|
||||
if (hasError) {
|
||||
if (hasError || !isDefined(authToken)) {
|
||||
// TODO: Add an error display component here
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SharedFrontComponentRenderer
|
||||
theme={theme}
|
||||
componentUrl={getMockFrontComponentUrl()}
|
||||
componentUrl={componentUrl}
|
||||
authToken={authToken}
|
||||
executionContext={executionContext}
|
||||
frontComponentHostCommunicationApi={frontComponentHostCommunicationApi}
|
||||
onError={handleError}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const mockFrontComponentCode = `
|
||||
var navigate = /* @__PURE__ */ (() => globalThis.TwentySdk.navigate)();
|
||||
|
||||
var AppPath = /* @__PURE__ */ (() => globalThis.TwentyShared["types"].AppPath)();
|
||||
|
||||
var Button = /* @__PURE__ */ (() => globalThis.RemoteComponents.TwentyUiButton)();
|
||||
|
||||
var jsx = /* @__PURE__ */ (() => globalThis.jsx)();
|
||||
|
||||
var TestComponent = () => {
|
||||
return /* @__PURE__ */ jsx(
|
||||
Button,
|
||||
{
|
||||
title: "Navigate to people index page",
|
||||
onClick: () => navigate?.(AppPath.RecordIndexPage, {
|
||||
objectNamePlural: "people"
|
||||
})
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
var test_component_front_component_default = globalThis.jsx(TestComponent, {});
|
||||
export {
|
||||
test_component_front_component_default as default
|
||||
};
|
||||
`;
|
||||
|
||||
let cachedMockBlobUrl: string | null = null;
|
||||
|
||||
export const getMockFrontComponentUrl = (): string => {
|
||||
if (isDefined(cachedMockBlobUrl)) {
|
||||
return cachedMockBlobUrl;
|
||||
}
|
||||
|
||||
const blob = new Blob([mockFrontComponentCode], {
|
||||
type: 'application/javascript',
|
||||
});
|
||||
cachedMockBlobUrl = URL.createObjectURL(blob);
|
||||
|
||||
return cachedMockBlobUrl;
|
||||
};
|
||||
@@ -15,6 +15,7 @@ const meta: Meta<typeof FrontComponentRenderer> = {
|
||||
},
|
||||
args: {
|
||||
onError: errorHandler,
|
||||
authToken: 'fake-token',
|
||||
},
|
||||
beforeEach: () => {
|
||||
errorHandler.mockClear();
|
||||
|
||||
@@ -19,6 +19,7 @@ import { componentRegistry } from '../generated/host-component-registry';
|
||||
|
||||
type FrontComponentContentProps = {
|
||||
componentUrl: string;
|
||||
authToken: string;
|
||||
executionContext: FrontComponentExecutionContext;
|
||||
frontComponentHostCommunicationApi: FrontComponentHostCommunicationApi;
|
||||
onError: (error?: Error) => void;
|
||||
@@ -27,6 +28,7 @@ type FrontComponentContentProps = {
|
||||
|
||||
export const FrontComponentRenderer = ({
|
||||
componentUrl,
|
||||
authToken,
|
||||
executionContext,
|
||||
frontComponentHostCommunicationApi,
|
||||
onError,
|
||||
@@ -43,6 +45,7 @@ export const FrontComponentRenderer = ({
|
||||
return (
|
||||
<FrontComponentWorkerEffect
|
||||
componentUrl={componentUrl}
|
||||
authToken={authToken}
|
||||
frontComponentHostCommunicationApi={frontComponentHostCommunicationApi}
|
||||
setReceiver={setReceiver}
|
||||
setThread={setThread}
|
||||
@@ -51,6 +54,7 @@ export const FrontComponentRenderer = ({
|
||||
);
|
||||
}, [
|
||||
componentUrl,
|
||||
authToken,
|
||||
frontComponentHostCommunicationApi,
|
||||
setError,
|
||||
setReceiver,
|
||||
|
||||
+4
-2
@@ -7,6 +7,7 @@ import { createRemoteWorker } from '../worker/createRemoteWorker';
|
||||
|
||||
type FrontComponentWorkerEffectProps = {
|
||||
componentUrl: string;
|
||||
authToken: string;
|
||||
frontComponentHostCommunicationApi: FrontComponentHostCommunicationApi;
|
||||
setReceiver: React.Dispatch<React.SetStateAction<RemoteReceiver | null>>;
|
||||
setThread: React.Dispatch<
|
||||
@@ -20,6 +21,7 @@ type FrontComponentWorkerEffectProps = {
|
||||
|
||||
export const FrontComponentWorkerEffect = ({
|
||||
componentUrl,
|
||||
authToken,
|
||||
frontComponentHostCommunicationApi,
|
||||
setReceiver,
|
||||
setThread,
|
||||
@@ -56,7 +58,7 @@ export const FrontComponentWorkerEffect = ({
|
||||
setThread(thread);
|
||||
|
||||
thread.imports
|
||||
.render(newReceiver.connection, { componentUrl })
|
||||
.render(newReceiver.connection, { componentUrl, authToken })
|
||||
.catch((error: Error) => {
|
||||
setError(error);
|
||||
});
|
||||
@@ -67,7 +69,7 @@ export const FrontComponentWorkerEffect = ({
|
||||
setThread(null);
|
||||
worker.terminate();
|
||||
};
|
||||
}, [componentUrl, setError, setReceiver, setThread]);
|
||||
}, [componentUrl, authToken, setError, setReceiver, setThread]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -15,9 +15,9 @@ import { jsx, jsxs } from 'react/jsx-runtime';
|
||||
import * as TwentySharedTypes from 'twenty-shared/types';
|
||||
import * as TwentySharedUtils from 'twenty-shared/utils';
|
||||
|
||||
import * as TwentySdk from '@/sdk';
|
||||
import { setFrontComponentExecutionContext } from '@/sdk/front-component-api/context/frontComponentContext';
|
||||
import { setNavigate } from '@/sdk/front-component-api/functions/navigate';
|
||||
import * as TwentySdk from '@/sdk';
|
||||
|
||||
import { type FrontComponentExecutionContext } from '../../types/FrontComponentExecutionContext';
|
||||
import { type FrontComponentHostCommunicationApi } from '../../types/FrontComponentHostCommunicationApi';
|
||||
@@ -47,11 +47,33 @@ const render: WorkerExports['render'] = async (
|
||||
root.connect(batchedConnection);
|
||||
document.body.append(root);
|
||||
|
||||
/* @vite-ignore */
|
||||
const componentModule = await import(renderContext.componentUrl);
|
||||
const response = await fetch(renderContext.componentUrl, {
|
||||
headers: { Authorization: `Bearer ${renderContext.authToken}` },
|
||||
});
|
||||
|
||||
const reactRoot = createRoot(root);
|
||||
reactRoot.render(componentModule.default);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch front component from ${renderContext.componentUrl}: ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const responseText = await response.text();
|
||||
|
||||
const blob = new Blob([responseText], {
|
||||
type: 'application/javascript',
|
||||
});
|
||||
|
||||
const importUrl = URL.createObjectURL(blob);
|
||||
|
||||
try {
|
||||
/* @vite-ignore */
|
||||
const componentModule = await import(importUrl);
|
||||
|
||||
const reactRoot = createRoot(root);
|
||||
reactRoot.render(componentModule.default);
|
||||
} finally {
|
||||
URL.revokeObjectURL(importUrl);
|
||||
}
|
||||
};
|
||||
|
||||
const initializeHostCommunicationApi: WorkerExports['initializeHostCommunicationApi'] =
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export type HostToWorkerRenderContext = {
|
||||
componentUrl: string;
|
||||
authToken: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user