fix(web): load installed plugin icons through the authenticated route

Multi-Workspace deployments fail the public plugin icon route closed (404), so plain <img src> icons broke everywhere locally and for the runner picker in production. A shared cached hook fetches the authenticated icon for installed plugins and leaves marketplace surfaces on the cloud URL.
This commit is contained in:
Hyu
2026-10-02 01:34:52 +08:00
parent 34b5fe0202
commit af9011f237
11 changed files with 434 additions and 112 deletions
+11 -39
View File
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { httpClient } from '@/app/infra/http/HttpClient';
import { useCurrentWorkspace } from '@/app/infra/http';
import { useInstalledPluginIcon } from '@/app/infra/hooks/useInstalledPluginIcon';
type AuthenticatedResourceState = {
key: string;
@@ -14,50 +15,21 @@ const EMPTY_RESOURCE: AuthenticatedResourceState = {
error: false,
};
/**
* Backwards-compatible wrapper around the shared installed-plugin icon hook.
* Kept for callers that want a `{ url, error }` shape; the url is '' while
* loading or when the icon is unavailable.
*/
export function useAuthenticatedPluginIcon(
author: string,
name: string,
enabled = true,
): { url: string; error: boolean } {
const [resource, setResource] =
useState<AuthenticatedResourceState>(EMPTY_RESOURCE);
const currentWorkspace = useCurrentWorkspace();
const workspaceUuid = currentWorkspace?.workspace.uuid;
const resourceKey = `${workspaceUuid ?? ''}:${author}/${name}`;
useEffect(() => {
if (!enabled) {
setResource({ key: resourceKey, url: '', error: false });
return;
}
let active = true;
let objectURL = '';
setResource({ key: resourceKey, url: '', error: false });
httpClient
.getAuthenticatedPluginIconURL(author, name)
.then((nextURL) => {
objectURL = nextURL;
if (active) {
setResource({ key: resourceKey, url: nextURL, error: false });
} else {
URL.revokeObjectURL(nextURL);
}
})
.catch(() => {
if (active) {
setResource({ key: resourceKey, url: '', error: true });
}
});
return () => {
active = false;
if (objectURL) URL.revokeObjectURL(objectURL);
};
}, [author, enabled, name, resourceKey]);
return {
url: resource.key === resourceKey ? resource.url : '',
error: resource.key === resourceKey && resource.error,
};
const url = useInstalledPluginIcon(
enabled ? author : null,
enabled ? name : null,
);
return { url: url ?? '', error: false };
}
export function useAuthenticatedPluginAsset(