Implement SCUM direct data plane

This commit is contained in:
npc0-hue
2026-08-13 16:33:11 +08:00
parent b07a792784
commit 8b236d7c15
56 changed files with 1466 additions and 90 deletions
@@ -85,10 +85,10 @@ describe("PluginPageHostPage", () => {
expect(html).not.toMatch(/sessionToken|componentKey|hostPath|dsn|runSocket|credential/i);
});
it("does not mount a bundle without client-side availability validation", () => {
it("renders the persisted SCUM dataset surface without a browser-side bundle dependency", () => {
const html = renderToStaticMarkup(<PluginPageHostPage {...props("")} initialPlugin={plugin} />);
expect(html).toContain("未绑定服务器");
expect(html).toContain("正在校验并加载插件页面 bundle");
expect(html).toContain("正在读取用户同步数据");
});
it("remains a manifest-driven host without SCUM component imports or game branches", () => {
@@ -100,6 +100,11 @@ describe("PluginPageHostPage", () => {
expect(hostSource).toContain("readyPluginRef.current = readyPlugin");
expect(hostSource).toContain("hostContextRef.current = hostContext");
expect(hostSource).toContain("listSCUMPlayers: () => platformApiClient.listSCUMPlayers(serverId)");
expect(hostSource).toContain("listSCUMUsers: () => platformApiClient.listSCUMUsers(serverId)");
expect(hostSource).toContain("listSCUMActivity: () => platformApiClient.listSCUMActivity(serverId)");
expect(hostSource).toContain("listSCUMGifts: () => platformApiClient.listSCUMGifts(serverId)");
expect(hostSource).toContain("listSCUMMapPoints: () => platformApiClient.listSCUMMapPoints(serverId)");
expect(hostSource).toContain("SCUMPersistedDataView");
expect(hostSource).toContain("createSCUMWorkflow: (request) => platformApiClient.createSCUMWorkflow(serverId, request as never)");
expect(hostSource).toContain("createSCUMOperation: (request) => platformApiClient.createSCUMOperation(serverId, request as never)");
expect(hostSource).toContain("listSCUMWorkflowSteps: (workflowId) => platformApiClient.listSCUMWorkflowSteps(serverId, workflowId)");
+14 -6
View File
@@ -5,6 +5,7 @@ import { useCallback, useEffect, useMemo, useRef, useState, type ComponentType }
import { platformApiClient } from "../api/client";
import type { GamePluginResponse } from "../api/types";
import { PageFrame } from "../components/PageFrame";
import { SCUMPersistedDataView, scumDatasetForPluginRoute } from "../components/SCUMPersistedDataView";
import { EmptyState, ErrorState, LoadingState } from "../components/StateViews";
import type { PageComponentProps } from "../contracts/page";
import { pluginBridgeManifestContractFromResponse } from "../contracts/pluginBridge";
@@ -69,8 +70,12 @@ export function PluginPageHostPage({ params, onNavigate, initialPlugin, embedded
const workspaceActions = useMemo<PluginPageWorkspaceActions | undefined>(() => {
if (!pluginId) return undefined;
return {
listSCUMUsers: () => platformApiClient.listSCUMUsers(serverId),
listSCUMPlayers: () => platformApiClient.listSCUMPlayers(serverId),
listSCUMSquads: () => platformApiClient.listSCUMSquads(serverId),
listSCUMActivity: () => platformApiClient.listSCUMActivity(serverId),
listSCUMGifts: () => platformApiClient.listSCUMGifts(serverId),
listSCUMMapPoints: () => platformApiClient.listSCUMMapPoints(serverId),
listSCUMSquadMembers: () => platformApiClient.listSCUMSquadMembers(serverId),
listSCUMVehicles: () => platformApiClient.listSCUMVehicles(serverId),
listSCUMFlags: () => platformApiClient.listSCUMFlags(serverId),
@@ -112,12 +117,14 @@ export function PluginPageHostPage({ params, onNavigate, initialPlugin, embedded
if (!hostContext) {
return <ErrorState title="插件页面不可用" reason="插件页面上下文初始化失败。" />;
}
const scumDataset = state.plugin.serverType === "scum" ? scumDatasetForPluginRoute(routeKey) : null;
if (embedded) {
return (
<>
{bundleError && <ErrorState title="插件页面不可用" reason={bundleError} />}
{!bundle && !bundleError && <LoadingState label="正在校验并加载插件页面 bundle…" compact />}
{bundle && React.createElement(bundle, { context: hostContext, workspaceActions, availability })}
{scumDataset && <SCUMPersistedDataView serverInstanceId={serverId} dataset={scumDataset} />}
{!scumDataset && bundleError && <ErrorState title="插件页面不可用" reason={bundleError} />}
{!scumDataset && !bundle && !bundleError && <LoadingState label="正在校验并加载插件页面 bundle…" compact />}
{!scumDataset && bundle && React.createElement(bundle, { context: hostContext, workspaceActions, availability })}
</>
);
}
@@ -145,9 +152,10 @@ export function PluginPageHostPage({ params, onNavigate, initialPlugin, embedded
<div className="action-list" aria-label="plugin page declarations"><span><strong>Bundle</strong> {page.bundleKey ? `${page.bundleKey}@${page.bundleVersion}` : "未声明"}</span><span><strong></strong> {page.bundleIntegritySha256 ? `${page.bundleIntegritySha256.slice(0, 18)}` : "未声明"}</span><span><strong>Companion</strong> {availability.available ? "可用" : "不可用"}</span></div>
</section>
{bundleError && <ErrorState title="插件页面不可用" reason={bundleError} />}
{!bundle && !bundleError && <LoadingState label="正在校验并加载插件页面 bundle…" />}
{bundle && React.createElement(bundle, { context: hostContext, workspaceActions, availability })}
{scumDataset && <SCUMPersistedDataView serverInstanceId={serverId} dataset={scumDataset} />}
{!scumDataset && bundleError && <ErrorState title="插件页面不可用" reason={bundleError} />}
{!scumDataset && !bundle && !bundleError && <LoadingState label="正在校验并加载插件页面 bundle…" />}
{!scumDataset && bundle && React.createElement(bundle, { context: hostContext, workspaceActions, availability })}
</div>
);
}