import { ArrowLeft, PlugZap } from "lucide-react"; import * as React from "react"; import { useCallback, useEffect, useMemo, useRef, useState, type ComponentType } from "react"; import { platformApiClient } from "../api/client"; import type { GamePluginResponse } from "../api/types"; import { PageFrame } from "../components/PageFrame"; import { EmptyState, ErrorState, LoadingState } from "../components/StateViews"; import type { PageComponentProps } from "../contracts/page"; import { pluginBridgeManifestContractFromResponse } from "../contracts/pluginBridge"; import type { PluginPageWorkspaceActions } from "../contracts/pluginPageHost"; import { createPluginBridgeDispatcher, createPluginBridgeHostContext } from "../utils/pluginBridgeHost"; import { loadPluginPageBundle, type PluginPageAvailability } from "../utils/pluginPageBundles"; type PluginPageState = | { status: "loading" } | { status: "error"; reason: string } | { status: "ready"; plugin: GamePluginResponse }; interface PluginPageHostPageProps extends PageComponentProps { initialPlugin?: GamePluginResponse; embedded?: boolean; } export function PluginPageHostPage({ params, onNavigate, initialPlugin, embedded = false }: PluginPageHostPageProps) { const pluginId = params.pluginId ?? ""; const routeKey = params.routeKey ?? ""; const serverId = params.serverId ?? ""; const [state, setState] = useState(() => initialPlugin ? { status: "ready", plugin: initialPlugin } : { status: "loading" }); const [bundle, setBundle] = useState; workspaceActions?: PluginPageWorkspaceActions; availability: PluginPageAvailability }> | null>(null); const [bundleError, setBundleError] = useState(""); const [availability, setAvailability] = useState({ available: false, reason: "正在验证插件桥接可用性。" }); const readyPluginRef = useRef(undefined); const hostContextRef = useRef | undefined>(undefined); const load = useCallback(async () => { if (!pluginId || !routeKey) { setState({ status: "error", reason: "插件页面路由缺少 pluginId 或 routeKey。" }); return; } setState({ status: "loading" }); try { const response = await platformApiClient.listGamePlugins(); const plugin = response.items.find((candidate) => candidate.id === pluginId); setState(plugin ? { status: "ready", plugin } : { status: "error", reason: "未找到已注册的插件声明。" }); } catch (error) { setState({ status: "error", reason: error instanceof Error ? error.message : "插件页面声明加载失败。" }); } }, [pluginId, routeKey]); useEffect(() => { if (!initialPlugin) { void load(); } }, [initialPlugin, load]); const readyPlugin = state.status === "ready" ? state.plugin : undefined; const declaredPage = readyPlugin?.pages.find((candidate) => candidate.key === routeKey); const manifestContract = useMemo(() => (readyPlugin ? pluginBridgeManifestContractFromResponse(readyPlugin) : undefined), [readyPlugin]); const declaredBundlePage = manifestContract?.pages.find((candidate) => candidate.key === routeKey); const hostContext = useMemo(() => manifestContract ? createPluginBridgeHostContext({ plugin: manifestContract, routeKey, serverInstanceId: serverId || undefined, themeTokens: { colorScheme: "dark", accentColor: "#7dd3fc" } }) : undefined, [manifestContract, routeKey, serverId]); readyPluginRef.current = readyPlugin; hostContextRef.current = hostContext; const workspaceActions = useMemo(() => { if (!pluginId) return undefined; return { pluginData: { list: (collection, key) => platformApiClient.listPluginData(serverId, collection, key), put: (collection, key, value) => platformApiClient.putPluginData(serverId, collection, key, value), delete: (collection, key) => platformApiClient.deletePluginData(serverId, collection, key), transact: (collection, mutations) => platformApiClient.transactPluginData(serverId, collection, mutations) }, gameClient: { queue: (request) => platformApiClient.queueGameClientBridgeCommand(serverId, request), get: (commandId) => platformApiClient.getGameClientBridgeCommand(serverId, commandId), list: (filter) => platformApiClient.listGameClientBridgeCommands(serverId, filter), snapshots: (query) => platformApiClient.listGameClientBridgeSnapshots(serverId, query) }, dispatch: (envelope, signal) => { const context = hostContextRef.current; if (!context) return Promise.resolve({ requestId: envelope.requestId, action: envelope.action, status: "error", error: { code: "host_unavailable", message: "plugin bridge host is unavailable" } }); return createPluginBridgeDispatcher(context, platformApiClient)(envelope, signal); } }; }, [pluginId, serverId]); const bundleLoadKey = declaredBundlePage ? [declaredBundlePage.bundleKey, declaredBundlePage.bundleVersion, declaredBundlePage.bundleIntegritySha256, declaredBundlePage.path].join(":") : ""; const loadableBundlePage = useMemo(() => declaredBundlePage, [bundleLoadKey]); useEffect(() => { let active = true; if (!loadableBundlePage) return () => { active = false; }; globalThis.__PLUGIN_PAGE_REACT__ = React; setBundle(null); setBundleError(""); void loadPluginPageBundle(loadableBundlePage).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) => { 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 : "无法验证插件桥接可用性。" }); }); return () => { active = false; }; }, [bundleLoadKey, loadableBundlePage, serverId]); if (state.status === "loading") { return ; } if (state.status === "error") { return void load()} />; } const page = declaredPage; if (!page) { return ; } if (!hostContext) { return ; } if (embedded) { return ( <> {bundleError && } {!bundle && !bundleError && } {bundle && React.createElement(bundle, { context: hostContext, workspaceActions, availability })} ); } return (
0 ? "success" : "warning" }, { label: "桥接动作", value: String(hostContext.bridgeActions.length), tone: hostContext.bridgeActions.length > 0 ? "success" : "warning" }, { label: "页面路由", value: routeKey, tone: "neutral" } ]} />

Bundle {page.bundleKey ? `${page.bundleKey}@${page.bundleVersion}` : "未声明"}完整性 {page.bundleIntegritySha256 ? `${page.bundleIntegritySha256.slice(0, 18)}…` : "未声明"}插件桥接 {availability.available ? "可用" : "不可用"}
{bundleError && } {!bundle && !bundleError && } {bundle && React.createElement(bundle, { context: hostContext, workspaceActions, availability })}
); }