fix(scum): gate plugin features by companion support
This commit is contained in:
@@ -35,8 +35,8 @@ const plugin: GamePluginResponse = {
|
||||
title: "文件与配置",
|
||||
path: "/files-config",
|
||||
bundleKey: "scum-server-plugin",
|
||||
bundleVersion: "1.0.0",
|
||||
bundleIntegritySha256: "sha256:8a4216107e1d7773d42a7e13b6466fd4fcf6e6bb2dc5f5af398fb7f4ea4f623b",
|
||||
bundleVersion: "1.0.1",
|
||||
bundleIntegritySha256: "sha256:8de5ec67248be72a6fa47df5e6f8c98e10092ade99e6fc066ceeba678b119c64",
|
||||
permissions: ["server.game-client.read", "server.game-client.command", "server.logs.read"],
|
||||
bridgeActions: ["server.instances.read", "logs.query"]
|
||||
}],
|
||||
@@ -79,7 +79,7 @@ describe("PluginPageHostPage", () => {
|
||||
const html = renderToStaticMarkup(<PluginPageHostPage {...props()} initialPlugin={plugin} />);
|
||||
expect(html).toContain("文件与配置");
|
||||
expect(html).toContain("平台托管上下文");
|
||||
expect(html).toContain("scum-server-plugin@1.0.0");
|
||||
expect(html).toContain("scum-server-plugin@1.0.1");
|
||||
expect(html).toContain("完整性");
|
||||
expect(html).toContain("返回服务器");
|
||||
expect(html).not.toMatch(/sessionToken|componentKey|hostPath|dsn|runSocket|credential/i);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { EmptyState, ErrorState, LoadingState } from "../components/StateViews";
|
||||
import type { PageComponentProps } from "../contracts/page";
|
||||
import { pluginBridgeManifestContractFromResponse } from "../contracts/pluginBridge";
|
||||
import { createPluginBridgeHostContext } from "../utils/pluginBridgeHost";
|
||||
import { loadPluginPageBundle } from "../utils/pluginPageBundles";
|
||||
import { loadPluginPageBundle, type PluginPageAvailability } from "../utils/pluginPageBundles";
|
||||
|
||||
type PluginPageState =
|
||||
| { status: "loading" }
|
||||
@@ -25,9 +25,9 @@ export function PluginPageHostPage({ params, onNavigate, initialPlugin }: Plugin
|
||||
const routeKey = params.routeKey ?? "";
|
||||
const serverId = params.serverId ?? "";
|
||||
const [state, setState] = useState<PluginPageState>(() => initialPlugin ? { status: "ready", plugin: initialPlugin } : { status: "loading" });
|
||||
const [bundle, setBundle] = useState<ComponentType<{ context: ReturnType<typeof createPluginBridgeHostContext>; workspace?: unknown; availability: { available: boolean; reason?: string } }> | null>(null);
|
||||
const [bundle, setBundle] = useState<ComponentType<{ context: ReturnType<typeof createPluginBridgeHostContext>; workspace?: unknown; availability: PluginPageAvailability }> | null>(null);
|
||||
const [bundleError, setBundleError] = useState("");
|
||||
const [availability, setAvailability] = useState<{ available: boolean; reason?: string }>({ available: false, reason: "正在验证 Companion 可用性。" });
|
||||
const [availability, setAvailability] = useState<PluginPageAvailability>({ available: false, reason: "正在验证 Companion 可用性。" });
|
||||
|
||||
const load = useCallback(async () => {
|
||||
if (!pluginId || !routeKey) {
|
||||
@@ -61,9 +61,7 @@ export function PluginPageHostPage({ params, onNavigate, initialPlugin }: Plugin
|
||||
void loadPluginPageBundle(declaredBundlePage).then((loaded) => { if (active) setBundle(() => loaded); }).catch((error) => { if (active) setBundleError(error instanceof Error ? error.message : "插件页面 bundle 加载失败。"); });
|
||||
if (!serverId) { setAvailability({ available: false, reason: "插件页面没有绑定服务器。" }); return () => { active = false; }; }
|
||||
void platformApiClient.getGameClientBridgeStatus(serverId).then((status) => {
|
||||
const requiredFeatures = declaredBundlePage.featureKeys ?? [];
|
||||
const unavailable = requiredFeatures.map((key) => status.features?.find((feature) => feature.key === key)).find((feature) => !feature?.available);
|
||||
if (active) setAvailability(unavailable ? { available: false, reason: unavailable.reason || `功能 ${unavailable.key} 没有兼容的 Companion 实现。` } : { available: status.available, reason: status.reason });
|
||||
if (active) setAvailability({ available: status.available, reason: status.reason, features: status.features });
|
||||
}).catch((error) => { if (active) setAvailability({ available: false, reason: error instanceof Error ? error.message : "无法验证 Companion 可用性。" }); });
|
||||
return () => { active = false; };
|
||||
}, [declaredBundlePage, serverId]);
|
||||
|
||||
@@ -4,12 +4,14 @@ import type { PluginBridgeHostContext, PluginPageContract } from "../contracts/p
|
||||
|
||||
export interface PluginPageBundleModule {
|
||||
pluginPageBundle: { key: string; version: string; integritySha256: string };
|
||||
renderPluginPage: (react: { createElement: typeof import("react").createElement }, input: { page: PluginPageContract; context: PluginBridgeHostContext; workspace?: unknown; availability: { available: boolean; reason?: string } }) => ReturnType<typeof import("react").createElement>;
|
||||
renderPluginPage: (react: { createElement: typeof import("react").createElement }, input: { page: PluginPageContract; context: PluginBridgeHostContext; workspace?: unknown; availability: PluginPageAvailability }) => ReturnType<typeof import("react").createElement>;
|
||||
}
|
||||
|
||||
export type PluginPageAvailability = { available: boolean; reason?: string; features?: Array<{ key: string; available: boolean; reason?: string }> };
|
||||
|
||||
const pageBundles = import.meta.glob<PluginPageBundleModule>("../../plugins/examples/*/page-bundle/index.ts");
|
||||
|
||||
export async function loadPluginPageBundle(page: PluginPageContract): Promise<ComponentType<{ context: PluginBridgeHostContext; workspace?: unknown; availability: { available: boolean; reason?: string } }>> {
|
||||
export async function loadPluginPageBundle(page: PluginPageContract): Promise<ComponentType<{ context: PluginBridgeHostContext; workspace?: unknown; availability: PluginPageAvailability }>> {
|
||||
if (!page.bundleKey || !page.bundleVersion || !page.bundleIntegritySha256) throw new Error("插件没有声明受验证的页面 bundle。");
|
||||
const match = Object.entries(pageBundles).find(([path]) => path.endsWith(`/${page.bundleKey}/page-bundle/index.ts`));
|
||||
if (!match) throw new Error("已声明的插件页面 bundle 未安装。");
|
||||
|
||||
Reference in New Issue
Block a user