diff --git a/src/frontend/src/components/plugins/PluginSource.tsx b/src/frontend/src/components/plugins/PluginSource.tsx index dff2c5c505..964c6144cf 100644 --- a/src/frontend/src/components/plugins/PluginSource.tsx +++ b/src/frontend/src/components/plugins/PluginSource.tsx @@ -1,4 +1,5 @@ import { generateUrl } from '../../functions/urls'; +import { useLocalState } from '../../states/LocalState'; /* * Load an external plugin source from a URL. @@ -32,13 +33,22 @@ export async function findExternalPluginFunction( source: string, functionName: string ): Promise { - // The source URL may also include the function name divided by a colon - // otherwise the provided function name will be used - if (source.includes(':')) { - [source, functionName] = source.split(':'); + const { getHost } = useLocalState.getState(); + + // Extract pathstring from the source URL + // Use the specified host unless the source is already a full URL + const url = new URL(source, getHost()); + + // If the pathname contains a ':' character, it indicates a function name + // but we need to remove it for the URL lookup to work correctly + if (url.pathname.includes(':')) { + const parts = url.pathname.split(':'); + source = parts[0]; // Use the first part as the source URL + functionName = parts[1] || functionName; // Use the second part as the + url.pathname = source; // Update the pathname to the source URL } - const module = await loadExternalPluginSource(source); + const module = await loadExternalPluginSource(url.toString()); if (module?.[functionName]) { return module[functionName]; diff --git a/src/frontend/src/components/plugins/RemoteComponent.tsx b/src/frontend/src/components/plugins/RemoteComponent.tsx index 76874eb377..5d5157bef2 100644 --- a/src/frontend/src/components/plugins/RemoteComponent.tsx +++ b/src/frontend/src/components/plugins/RemoteComponent.tsx @@ -9,6 +9,7 @@ import { type Root, createRoot } from 'react-dom/client'; import { api, queryClient } from '../../App'; import { ApiProvider } from '../../contexts/ApiContext'; import { LanguageContext } from '../../contexts/LanguageContext'; +import { useLocalState } from '../../states/LocalState'; import { Boundary } from '../Boundary'; import { findExternalPluginFunction } from './PluginSource'; @@ -43,19 +44,17 @@ export default function RemoteComponent({ undefined ); - const sourceFile = useMemo(() => { - return source.split(':')[0]; - }, [source]); + const func: string = useMemo(() => { + // Attempt to extract the function name from the source + const { getHost } = useLocalState.getState(); + const url = new URL(source, getHost()); - // Determine the function to call in the external plugin source - const functionName = useMemo(() => { - // The "source" string may contain a function name, e.g. "source.js:myFunction" - if (source.includes(':')) { - return source.split(':')[1]; + if (url.pathname.includes(':')) { + const parts = url.pathname.split(':'); + return parts[1] || defaultFunctionName; // Use the second part as the function name, or fallback to default + } else { + return defaultFunctionName; } - - // By default, return the default function name - return defaultFunctionName; }, [source, defaultFunctionName]); const reloadPluginContent = useCallback(() => { @@ -68,8 +67,8 @@ export default function RemoteComponent({ reloadContent: reloadPluginContent }; - if (sourceFile && functionName) { - findExternalPluginFunction(sourceFile, functionName) + if (source && defaultFunctionName) { + findExternalPluginFunction(source, func) .then((func) => { if (!!func) { try { @@ -99,30 +98,28 @@ export default function RemoteComponent({ console.error(error); } } else { - setRenderingError(`${sourceFile}:${functionName}`); + setRenderingError(`${source} / ${func}`); } }) .catch((_error) => { console.error( - `ERR: Failed to load remove plugin function: ${sourceFile}:${functionName}` + `ERR: Failed to load remote plugin function: ${source} /${func}` ); }); } else { setRenderingError( - `${t`Invalid source or function name`} - ${sourceFile}:${functionName}` + `${t`Invalid source or function name`} - ${source} /${func}` ); } - }, [componentRef, rootElement, sourceFile, functionName, context]); + }, [componentRef, rootElement, source, defaultFunctionName, context]); // Reload the plugin content dynamically useEffect(() => { reloadPluginContent(); - }, [sourceFile, functionName, context, rootElement]); + }, [source, func, context, rootElement]); return ( - + {renderingError && (