按类型开发指南

外部服务与带页面的插件(external_service / ui_page)开发指南

external_service 是你自己服务器上的 HTTPS 服务,订阅 Webhook 事件、调用开放 API;ui_page 把你自己 origin 上的页面以沙箱 iframe 嵌进主控菜单或站点“扩展”页签。两者的代码都不进主控进程。

← 全部开发指南

快速开始

  1. external_service:新建插件选 external_service,manifest 入口为 external_https,用 subscriptions 声明要订阅的事件、permissions 声明对应的 events.*.read 权限,上传 1–16 个平台包。
  2. ui_page:先在卖家站“页面来源”登记并验证你的 origin(以及 api_base_url 的 origin):把 cdnca-plugin-origin=<令牌> 放在 https://<主机>/.well-known/cdnca-plugin-origin.txt。新建插件选 ui_page,上传时不带任何包或文件。
  3. 调用主控:当前用租户在主控创建并交给你的 API Key(Authorization: CdncaKey <key_id>.<secret>),只能调 OpenAPI 标注“API Key 可调”的接口。对第三方公开的插件工作负载凭据尚未发布。

签名与公钥登记见插件签名工具页。 签名工具与步骤 →

manifest 示例与字段

"entrypoint": {"type": "external_https", "base_url": "https://hooks.acme.example", "health_path": "/healthz"},
"permissions": ["events.site.read", "events.security.read"],
"subscriptions": ["io.cdnca.site.v1.updated", "io.cdnca.security.v1.incident_detected"]
"entrypoint": {
  "type": "ui_page",
  "origin": "https://ui.acme.example",
  "api_base_url": "https://api.acme.example/v1",
  "pages": [{
    "id": "report", "path": "/report", "mount": "menu", "menu_parent": "monitor",
    "role": "admin", "title": {"zh-CN": "报表", "en-US": "Reports"}, "required_permission": "sites.read"
  }]
},
"permissions": ["platform.ui_pages.provide"]
字段要求
subscriptions[]最多 64 个事件类型,形如 io.cdnca.site.v1.updated
webhooks[]最多 64 个可接收的 Webhook 能力码,每个安装单独批准
originhttps://<DNS 主机名>,默认端口,可带结尾 /;必须是你已验证的页面来源
api_base_url可选,https,主控把页面的受限 API 请求代理到这里;其 origin 也必须已验证
pages[]1–16 页:id ^[a-z][a-z0-9-]{0,31}$、path(以 / 开头)、mount(menu 或 site_tab)、role(admin 或 user)、title(zh-CN 必填,最多 32 字)
menu_parentmount=menu 时必填:admin 为 monitor/node/site/stream/config/package/finance/system,user 为 monitor/site/stream/user-package/account;site_tab 不能填
required_permission / required_module可选的显示门槛(权限码不能是 platform.admin;模块形如 module.xxx、pack.xxx、theme.xxx)
permissionsui_page 必须包含 platform.ui_pages.provide;ui_page 不能声明 provides

协议与接口

Webhook 事件

7 类领域事件:部署状态、站点更新、证书状态、证书到期、节点状态、用量阈值、安全事件,每类要求安装获批对应的 events.*.read 权限。投递 POST,Content-Type: application/cloudevents+json,至少一次;可按站点过滤、补发死信、按时间重放。

  1. 先对原始正文算 SHA-256,与 X-CDNCA-Webhook-Content-Sha256 常量时间比较。
  2. 按 X-CDNCA-Webhook-Key-Id 选密钥(43 字符 base64url 解码为 32 字节),重建 v2 HMAC 与 X-CDNCA-Webhook-Signature 比较。
  3. 拒绝与本地时间相差超过 300 秒的 X-CDNCA-Webhook-Timestamp。
  4. 按 delivery ID 去重,再解析 JSON,用事件 ID 做业务幂等;业务成功后才返回 2xx。
cdnca.webhook.signature/v2
POST
<canonical-escaped-request-path>
<timestamp>
<event-id>
<delivery-id>
<secret-version-id>
<lowercase-hex-sha256-of-raw-body>

ui_page 协议

页面在 <iframe sandbox="allow-scripts allow-forms"> 里运行(没有 allow-same-origin),拿不到主控的 Cookie、存储和用户凭据。页面从 URL 片段 #cdnca-nonce=<nonce> 读 nonce,向 parent 发 cdnca.hello,宿主回 cdnca.ready 并交出一个 MessagePort;之后只有 context.get、navigate、api.request 三个方法,其他消息回 message_not_allowed。

api.request 经主控 POST /v1/plugin-ui/{plugin_id}/proxy 转到 api_base_url(只连公网、不跟随重定向、只回 JSON、每用户每插件每分钟 120 次)。主控附带 X-Cdnca-Plugin-Context(60 秒有效,含 nonce)、X-Cdnca-Plugin-Signature(安装私钥 Ed25519)与 X-Cdnca-Plugin-Key-Id,你的后端用安装公钥验签并记住 nonce。SDK 与验签示例见 ui_page SDK 页。

打包与上传字段

表单字段必填内容与校验
manifest是签名后的 manifest,1 字节到 256 KiB;身份必须是本插件 ID、本命名空间,integrity.key_id 必须是该命名空间 active 的公钥并验签通过
configuration_schema是配置 JSON Schema 文件,1 字节到 64 KiB;SHA-256 必须等于 spec.configuration_schema.sha256
changelog_md否更新说明 Markdown,最多 65536 字节
channel否stable(默认)或 beta
auto_publish否true / false:审核通过后自动上架
package:<platform>external_service:是,1–16 个;ui_page:不能有平台名匹配 ^[a-z0-9]+-[a-z0-9_]+$;每个包默认最大 64 MiB,格式为 zip、tar.gz 或 tar
content不能有两种入口都没有数据包文件

接口:POST /seller/v1/plugins/{id}/versions,Content-Type: multipart/form-data,必须带一个 1–128 字符的 Idempotency-Key 请求头;同一 key 重试返回同一结果。请求体默认上限 200 MiB。除上表与本类型专属字段外,出现任何其他字段名都返回 400 invalid_upload。

同一插件的版本号不能重复,且必须高于任何已审核过的版本(已通过、已上架、已隐藏或已下架)。上传成功后版本进入“预检中”,卖家站版本详情显示预检报告。

审核规则

上传后审核 worker 先跑“校验”阶段(manifest、入口、权限、本类型专属检查),出现任何拒绝项就不再扫描;再跑“扫描”阶段。结论:有拒绝项 → 需修改(修好后点“重新提交”,每个版本最多 10 次扫描);有隔离项 → 待人工审核;都没有 → 视下列条件直接通过或进入人工审核。

  • 首个版本(该插件还没有任何已审核版本)
  • 权限与上一个已审核版本不同(增加或减少)
  • 高风险类型:module、engine_module、provider
  • 入驻未满 90 天的卖家(市场默认值,运营可调)
  • 扫描连续 3 次出错

满足任一条即进入人工审核。完整规则表(规则 ID、级别、拒绝或隔离、是否试运行)见审核规则页。

  • ui_page.declaration_invalid:origin、pages、挂载点、菜单父级、标题或显示门槛不合法,detail 给出 field 与 reason(拒绝)。
  • ui_page.origin_unverified:origin 或 api_base_url 不是你验证过的来源(拒绝)。
  • ui_page.permission_missing / ui_page.artifacts_present:缺 platform.ui_pages.provide,或版本带了包或文件(拒绝)。
  • external_service 的平台包与 provider 一样过 package、secrets、dependencies、malware、diff 各项扫描。

本类型相关的规则见审核规则清单: metadata.* entrypoint.* permissions.* ui_page.* package.* secrets.* dependencies.* malware.* diff.*

版本与兼容

metadata.version 是规范 SemVer(不带前导 v),每次上传必须比已审核版本高;同一版本号不能重复上传。

spec.compatibility.control_api 是 1–8 个以空格分隔的比较式(>=、<=、=、>、<,后接规范 SemVer),总长不超过 128;全部满足才算兼容。不满足的主控不能安装或升级到该版本,已安装的保持最近兼容版本。

"compatibility": {"control_api": ">=1.0.35 <2.0.0"}

新版本增加权限时,客户管理员在主控里要重新确认;权限变化的版本一律人工审核。

事件类型里的 vN 与 manifest api_version、OpenAPI 版本各自演进;同一事件大版本只增加可选字段。

本地调试

  • Webhook:主控“插件 Webhook 订阅”可以发测试事件(io.cdnca.webhook.v1.test)联调验签;仓库 docs/developer/examples/webhook-v2-receiver.md 是可运行的参考接收端。
  • ui_page:docs/developer/examples/plugin-ui-sdk/ 有框内 SDK、示例页、Node 后端验签和金标向量,node --test 可以直接跑。
  • manifest:仓库 docs/developer/examples/validate-plugin-manifest.mjs 按 schema、扩展目录和兼容矩阵离线校验 manifest 并验签。
node --test docs/developer/examples/plugin-ui-sdk/sdk.test.mjs
node docs/developer/examples/validate-plugin-manifest.mjs --control-api-version 1.0.35 plugin.json publisher-public-key.json