PluginService

# PluginService

管理插件的完整生命周期:加载、卸载、查询元数据、访问跨插件资源。

import { useService, useServiceState } from '@autumnbox/sdk/hooks';
import { PluginService } from '@autumnbox/sdk/hooks';

# 查询插件列表

const pluginService = useService(PluginService);

// 获取所有已加载插件的元数据(响应式)
const plugins = useServiceState(pluginService.pluginsState);

每个插件元数据包含:

interface PluginMetadata {
  pluginPackageName: string;   // 唯一标识
  name: string;                // 显示名称(已翻译)
  description: string;         // 描述(已翻译)
  author: string;              // 作者名(已翻译)
  version: string;             // 版本号
  license?: string;            // 许可证
  repository?: string;         // 源码仓库 URL
}

# 查询单个插件

const meta = pluginService.getPluginMetadata('autumnbox.main-plugin');
if (meta) {
  console.log(`${meta.name} v${meta.version} by ${meta.author}`);
}

# 检查是否内置

const isBuiltin = pluginService.isBuiltin('autumnbox.main-plugin');
// true → 不可卸载的内置插件

# 访问跨插件资源

PluginService 可以读取任何已加载插件的打包资源:

// 获取原始 Blob
const blob = pluginService.getPluginResource(
  'autumnbox.plugin-store', 'icon.png'
);

// 获取文本
const html = await pluginService.getPluginResourceAsText(
  'autumnbox.plugin-store', 'templates/card.html'
);

// 获取 Data URI(自动推断 MIME,适合 img src)
const dataUri = await pluginService.getPluginResourceAsSource(
  'autumnbox.plugin-store', 'icon.png'
);

// 列出目录内容
const files = pluginService.listResource(
  'autumnbox.plugin-store', 'templates'
);
// → ['card.html', 'detail.html']

Data URI 与 MIME

getPluginResourceAsSource 会根据扩展名自动设置正确的 MIME 类型。这对 SVG 至关重要——浏览器拒绝渲染没有 image/svg+xml MIME 的 SVG data URI。

# 安装与卸载

# 动态安装插件

// 从 File 对象安装(如用户通过 input[type=file] 选择的 .atmb)
const fileInput = document.querySelector('input[type=file]') as HTMLInputElement;
const file = fileInput.files?.[0];

if (file) {
  const pluginName = await pluginService.loadPlugin(file, {
    persist: true,     // 持久化到 OPFS,刷新后保留
    uninstallable: true, // 允许卸载
  });
  console.log(`已安装: ${pluginName}`);
}

# 卸载插件

// 立即卸载
const success = await pluginService.uninstallPlugin('autumnbox.old-plugin');

// 或标记为"下次重载时移除"
await pluginService.addToBeRemoved('autumnbox.old-plugin');

# 更新插件

// 标记待更新(新 .atmb 文件在下次重载时替换旧版)
await pluginService.readyForUpdate('autumnbox.my-plugin', newAtmbFile);

// 触发深度重载
pluginService.deepReload();

# 打开插件的 App

// 打开某个插件注册的所有 App(类似"展开插件"功能)
pluginService.openAppsOf('autumnbox.main-plugin');

// 取消打开
pluginService.dismissOpenApps();

# 待处理操作状态

// 是否有待卸载的插件
const pendingRemovals = useServiceState(pluginService.pendingRemovalsState);
// → ['autumnbox.old-plugin']

// 是否有待更新的插件
const pendingUpdates = useServiceState(pluginService.pendingUpdatesState);

// 是否有任何待处理操作
if (pluginService.hasPendingOperations) {
  // 提示用户需要重载
}

# 实战示例:插件列表页

import React from 'react';
import { List, Avatar, Tag, Button, Typography } from 'antd';
import { useService, useServiceState } from '@autumnbox/sdk/hooks';
import { PluginService } from '@autumnbox/sdk/hooks';

const { Text } = Typography;

const PluginList: React.FC = () => {
  const pluginService = useService(PluginService);
  const plugins = useServiceState(pluginService.pluginsState);

  return (
    <List
      dataSource={plugins}
      renderItem={(plugin) => (
        <List.Item
          actions={[
            !pluginService.isBuiltin(plugin.pluginPackageName) && (
              <Button
                danger
                size="small"
                onClick={() => pluginService.uninstallPlugin(plugin.pluginPackageName)}
              >
                卸载
              </Button>
            ),
          ].filter(Boolean)}
        >
          <List.Item.Meta
            avatar={<PluginIcon pluginPackageName={plugin.pluginPackageName} />}
            title={
              <>
                {plugin.name}
                <Tag style={{ marginLeft: 8 }}>v{plugin.version}</Tag>
              </>
            }
            description={plugin.description}
          />
        </List.Item>
      )}
    />
  );
};

const PluginIcon: React.FC<{ pluginPackageName: string }> = ({ pluginPackageName }) => {
  const pluginService = useService(PluginService);
  const [src, setSrc] = React.useState<string>();

  React.useEffect(() => {
    pluginService.getPluginResourceAsSource(pluginPackageName, 'icon.png')
      .then((uri) => { if (uri) setSrc(uri); });
  }, [pluginPackageName]);

  return <Avatar src={src} shape="square" />;
};

# 下一步