这是一个高级主题,用于基于插件注册表构建您自己的软件。如果您只想在 EmDash 站点上安装插件,则不需要任何这些内容——在配置中启用注册表并使用管理面板即可。
注册表的发现端是一个公共的只读 API。@emdash-cms/registry-client 包对其进行了封装,因此您可以在 EmDash 之外构建插件目录、搜索页面或发布动态。该客户端可在任何 fetch 可用的环境中运行——Node、Workers、浏览器或 Astro 站点。
安装
discovery 子路径不携带认证或 OAuth 依赖。安装客户端并固定到确切版本:
npm install @emdash-cms/[email protected]
列出和解析包
以下 Astro 页面列出注册表中的所有插件:
---
import { DiscoveryClient } from "@emdash-cms/registry-client/discovery";
const discovery = new DiscoveryClient({
aggregatorUrl: "https://registry.emdashcms.com",
});
const { packages } = await discovery.searchPackages({ q: "", limit: 50 });
---
<ul>
{
packages.map((pkg) => (
<li>
<a href={`/plugins/${pkg.did}/${pkg.slug}`}>
{pkg.profile?.name ?? pkg.slug}
</a>
{pkg.latestVersion && <span>v{pkg.latestVersion}</span>}
<p>{pkg.profile?.description}</p>
</li>
))
}
</ul>
链接使用 pkg.did,它始终存在。发布者 handle 是尽力而为的,可能不存在,因此不要基于它构建 URL。
包详情页面通过 DID 和 slug 获取插件,然后获取最新发布版本:
import { DiscoveryClient } from "@emdash-cms/registry-client/discovery";
const discovery = new DiscoveryClient({
aggregatorUrl: "https://registry.emdashcms.com",
});
export async function getPlugin(did: string, slug: string) {
const pkg = await discovery.getPackage({ did, slug });
const latest = await discovery.getLatestRelease({
did: pkg.did,
package: pkg.slug,
});
return { pkg, latest };
}
发现方法
客户端为每个聚合器查询暴露一个方法:
searchPackages({ q, capability?, limit?, cursor? })— 自由文本搜索,可选择按声明给定访问类别的包进行过滤。返回{ packages, cursor? }。resolvePackage({ handle, slug })— 通过 handle 和 slug 解析包。getPackage({ did, slug })— 通过 DID 和 slug 获取包。listReleases({ did, package, limit?, cursor? })— 包的所有发布版本,最新版本排在前面。getLatestRelease({ did, package })— 最高的已批准、未撤回的发布版本。
getPackageStatus() 和 resolvePackageStatus() 封装了对应的包查询,并将安全的 ListingUnavailable 响应映射为 { status: "unavailable" }。在需要区分已索引但未批准的列表与不存在的包的用户界面中使用这些方法,而无需渲染发布者控制的错误内容。
处理不受信任的记录
聚合器是一个不受信任的索引,中继它未撰写的记录,因此客户端在边界处验证每条记录。由此产生两条规则:
profile和release字段可以为null。 当中继的记录验证失败时,客户端将其呈现为null而不是使整个调用失败,这样一条格式错误的记录不会清空搜索页面。在读取pkg.profile?.name或latest.release?.artifacts.package之前始终进行空值检查。- 在渲染之前自行验证 URL 协议。 验证检查结构而非 URL 安全性——
uri字段可以携带javascript:协议。在将任何注册表提供的 URL 放入href或src之前,应用您自己的http/https允许列表。
非 2xx 响应会抛出 ClientResponseError(从包中重新导出),携带 .error、.description、.status 和 .headers。参考聚合器仅返回由每个所需正面标签来源批准的确切 CID 修订版本。atproto-accept-labelers 头声明为请求和缓存标识配置的裸 DID。聚合器验证声明,但其配置的策略保持权威性。
按宿主兼容性过滤
发布版本可以在其 requires 块中声明环境要求(EmDash 或 Astro 版本范围)。@emdash-cms/registry-client/env 子路径对其进行评估,以便目录可以标记不会在给定宿主上运行的发布版本:
import { checkEnvCompatibility, hostEnvFromVersions } from "@emdash-cms/registry-client/env";
import type { ValidatedReleaseView } from "@emdash-cms/registry-client/discovery";
const host = hostEnvFromVersions("0.17.0", "5.6.0");
// 传入 getLatestRelease() 的结果。当发布版本可在此宿主上运行时,返回的数组为空。
export function envMismatches(latest: ValidatedReleaseView) {
return checkEnvCompatibility(latest.release?.requires, host);
}