feat(plugin): gate pages on companion features
This commit is contained in:
@@ -60,8 +60,11 @@ export interface GameClientBridgePageContractResponse {
|
||||
commandTypes?: string[];
|
||||
snapshotTypes?: string[];
|
||||
queryTemplateKeys?: string[];
|
||||
featureKeys?: string[];
|
||||
}
|
||||
|
||||
export interface GameClientBridgeFeatureDeclarationResponse { key: string; title: string; permission: string; requiredHandlers?: string[]; requiredEventProducers?: string[]; }
|
||||
|
||||
export interface GameClientBridgeCompanionDeclarationResponse {
|
||||
profileKey: string;
|
||||
configTemplateKey: string;
|
||||
@@ -85,6 +88,7 @@ export interface GameClientBridgeManifestResponse {
|
||||
commandRetentionSeconds: number;
|
||||
maxCommands: number;
|
||||
pages?: GameClientBridgePageContractResponse[];
|
||||
features?: GameClientBridgeFeatureDeclarationResponse[];
|
||||
companion?: GameClientBridgeCompanionDeclarationResponse;
|
||||
}
|
||||
|
||||
@@ -96,14 +100,19 @@ export interface GameClientBridgeProfileDeclarationResponse {
|
||||
commandTypes: string[];
|
||||
snapshotTypes: string[];
|
||||
queryTemplateKeys: string[];
|
||||
handlerTypes?: string[];
|
||||
eventProducerTypes?: string[];
|
||||
}
|
||||
|
||||
export interface GameClientBridgeFeatureAvailabilityResponse { key: string; available: boolean; reason?: string; }
|
||||
|
||||
export interface GameClientBridgeStatusResponse {
|
||||
serverInstanceId: string;
|
||||
pluginId: string;
|
||||
available: boolean;
|
||||
reason?: string;
|
||||
profiles: GameClientBridgeProfileDeclarationResponse[];
|
||||
features?: GameClientBridgeFeatureAvailabilityResponse[];
|
||||
}
|
||||
|
||||
export interface GameClientBridgeCommandResultResponse {
|
||||
@@ -224,6 +233,7 @@ export interface GamePluginPageResponse {
|
||||
bundleIntegritySha256?: string;
|
||||
permissions: string[];
|
||||
bridgeActions?: string[];
|
||||
featureKeys?: string[];
|
||||
}
|
||||
|
||||
export interface RuntimeDiscoveryProbeResponse {
|
||||
|
||||
@@ -83,6 +83,7 @@ export interface PluginPageContract {
|
||||
bundleIntegritySha256?: string;
|
||||
permissions?: PluginPermission[];
|
||||
bridgeActions?: PluginBridgeAction[];
|
||||
featureKeys?: string[];
|
||||
}
|
||||
|
||||
export interface PluginBridgeManifestContract {
|
||||
@@ -108,7 +109,8 @@ export function pluginBridgeManifestContractFromResponse(
|
||||
bundleVersion: page.bundleVersion,
|
||||
bundleIntegritySha256: page.bundleIntegritySha256,
|
||||
permissions: page.permissions.filter(isPluginPermission),
|
||||
bridgeActions: page.bridgeActions?.filter(isPluginBridgeAction)
|
||||
bridgeActions: page.bridgeActions?.filter(isPluginBridgeAction),
|
||||
featureKeys: page.featureKeys ? [...page.featureKeys] : []
|
||||
})),
|
||||
aiPurposes: [...plugin.aiPurposes]
|
||||
};
|
||||
|
||||
@@ -60,7 +60,11 @@ export function PluginPageHostPage({ params, onNavigate, initialPlugin }: Plugin
|
||||
setBundle(null); setBundleError("");
|
||||
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) => { if (active) setAvailability({ available: status.available, reason: status.reason }); }).catch((error) => { if (active) setAvailability({ available: false, reason: error instanceof Error ? error.message : "无法验证 Companion 可用性。" }); });
|
||||
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 });
|
||||
}).catch((error) => { if (active) setAvailability({ available: false, reason: error instanceof Error ? error.message : "无法验证 Companion 可用性。" }); });
|
||||
return () => { active = false; };
|
||||
}, [declaredBundlePage, serverId]);
|
||||
|
||||
|
||||
@@ -69,7 +69,8 @@ export function parseSafeGameClientBridgeStatus(value: unknown): GameClientBridg
|
||||
pluginId: string(record.pluginId, "pluginId"),
|
||||
available: boolean(record.available, "available"),
|
||||
reason: optionalString(record.reason, "reason"),
|
||||
profiles: array(record.profiles, "profiles").map(parseProfile)
|
||||
profiles: array(record.profiles, "profiles").map(parseProfile),
|
||||
features: array(record.features ?? [], "features").map(parseFeature)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -136,10 +137,17 @@ function parseProfile(value: unknown): GameClientBridgeProfileDeclarationRespons
|
||||
reason: optionalString(record.reason, "profile.reason"),
|
||||
commandTypes: stringArray(record.commandTypes, "profile.commandTypes"),
|
||||
snapshotTypes: stringArray(record.snapshotTypes, "profile.snapshotTypes"),
|
||||
queryTemplateKeys: stringArray(record.queryTemplateKeys, "profile.queryTemplateKeys")
|
||||
queryTemplateKeys: stringArray(record.queryTemplateKeys, "profile.queryTemplateKeys"),
|
||||
handlerTypes: stringArray(record.handlerTypes ?? [], "profile.handlerTypes"),
|
||||
eventProducerTypes: stringArray(record.eventProducerTypes ?? [], "profile.eventProducerTypes")
|
||||
};
|
||||
}
|
||||
|
||||
function parseFeature(value: unknown): { key: string; available: boolean; reason?: string } {
|
||||
const record = safeObject(value, "Game Client Bridge feature");
|
||||
return { key: string(record.key, "feature.key"), available: boolean(record.available, "feature.available"), reason: optionalString(record.reason, "feature.reason") };
|
||||
}
|
||||
|
||||
function parseResult(value: unknown): GameClientBridgeCommandResultResponse {
|
||||
const record = safeObject(value, "Game Client Bridge command result");
|
||||
const result: GameClientBridgeCommandResultResponse = {
|
||||
|
||||
Reference in New Issue
Block a user