Files
ttstd-infinite-canvas/web/src/lib/plugins/plugin-permission-check.ts

64 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 编辑器插件执行期权限校验,默认拒绝未知插件和缺失权限。
// v2 插件贡献 editorSlots 或代发 timeline 命令时,manifest.permissions 必须覆盖对应能力域;
// v1 插件不贡献 editorSlots,因此不经过这条插槽权限路径。本模块只做纯判定,
// 接入点分别位于插槽渲染(SlotStack)和宿主命令代发通道。
import { getRegisteredPlugin } from "@/lib/plugins/plugin-registry";
import type { EditorPluginPermission, EditorSlotKind } from "@/lib/plugins/plugin-types";
/** 每个编辑器插槽所需的最小权限域;新增插槽必须补表,否则会因找不到权限而拒绝渲染。 */
export const EDITOR_SLOT_REQUIRED_PERMISSION: Record<EditorSlotKind, EditorPluginPermission> = {
"timeline-panel": "timeline.command",
"preview-renderer": "timeline.read",
inspector: "timeline.command",
"asset-ingest": "timeline.command",
"subtitle-tool": "timeline.command",
"transcription-provider": "timeline.command",
"export-renderer": "export.run",
"ai-assistant": "timeline.read",
};
export type EditorSlotPermissionVerdict =
| { allowed: true }
| { allowed: false; reason: "plugin-not-registered" | "missing-permission"; missing: EditorPluginPermission | null };
/** fail-closed 权限判定:权限集缺失或未包含 required 一律拒绝。 */
export function checkEditorPermission(permissions: ReadonlySet<string> | null, required: EditorPluginPermission): boolean {
return permissions !== null && permissions.has(required);
}
export function requiredPermissionForEditorSlot(slot: EditorSlotKind): EditorPluginPermission {
return EDITOR_SLOT_REQUIRED_PERMISSION[slot];
}
/** 查询插件的权限集(未注册返回 null)。 */
export function pluginPermissions(pluginId: string): ReadonlySet<string> | null {
const registered = getRegisteredPlugin(pluginId);
if (!registered) return null;
return new Set(registered.manifest.permissions ?? []);
}
/** 判定某插件是否被允许渲染某编辑器插槽(fail-closed;v2 注册即被检,缺失权限拒渲染)。 */
export function pluginMayRenderEditorSlot(pluginId: string, slot: EditorSlotKind): EditorSlotPermissionVerdict {
const permissions = pluginPermissions(pluginId);
if (permissions === null) {
return { allowed: false, reason: "plugin-not-registered", missing: null };
}
const required = EDITOR_SLOT_REQUIRED_PERMISSION[slot];
if (!permissions.has(required)) {
return { allowed: false, reason: "missing-permission", missing: required };
}
return { allowed: true };
}
/** 宿主命令代发通道的权限断言:未注册插件或未声明目标权限时抛错,调用方必须阻止命令执行。 */
export function assertEditorPermission(pluginId: string, permission: EditorPluginPermission): void {
const permissions = pluginPermissions(pluginId);
if (permissions === null) {
throw new Error(`插件 ${pluginId} 未注册,无法代发 ${permission} 命令`);
}
if (!permissions.has(permission)) {
throw new Error(`插件 ${pluginId} 缺少 ${permission} 权限,命令已被拒绝`);
}
}