跳到主要内容
TABTAB

导航

市场与签名

索引是一份小 JSON,置于静态托管之上;安装包走同源直链。两者的关联靠 SHA-256,信任靠签名。

为什么分成两份

索引与安装包分离:索引只有几十 KB,可以随时拉、可以缓存在本地; 安装包可能几十 MB,不需要每次都下载。用户每次启动只付索引的钱。

拉取失败时宿主保留旧缓存 —— 断网不该让用户失去已经能看到的内容。

自建源时,索引由你的发布流程生成:包内 manifest 决定 id 与版本, 文件本身决定 sha256,发布地址决定 url。这三样都不需要人再抄一遍 —— 抄错的后果不是「某个插件装不上」,而是整份索引拉取失败, 你的用户看到的只会是「市场打不开」。

索引长什么样

market/index.json
{
  "updatedAt": "2026-10-14T03:44:08.159Z",
  "signature": "X8eAOfDuNL5RcV5pZavB7QFoiA6ZPg/DwS/zHOF7y57jAcQCH2RQFcu14czfeapZh5oly0TXe5lsbglxGJpFAQ==",
  "plugins": [
    {
      "id": "dev.tabtab.plugin.calculator",
      "name": "计算器",
      "version": "1.0.0",
      "minHostVersion": "0.1.0",
      "sha256": "4887af8fc2d07fddab0ad724668a0b575864803b25b8c63ac2a0e0363dac09a4",
      "url": "https://<你的域名>/market/<插件名>-<版本号>.zip",
      "description": "能算整条算式的计算器:四则、幂、阶乘、三角与对数函数,错误精确标到出错的字符。每次算完自动记进历史,历史可搜索、可复用、可删除。",
      "author": "TABTAB",
      "tags": [
        "计算器",
        "工具",
        "剪贴板"
      ],
      "permissions": [
        "storage",
        "clipboard:read",
        "clipboard:write"
      ],
      "official": true,
      "signature": "",
      "icon": "https://tabtab.dev/market/icons/calculator.svg",
      "updatedAt": "2026-10-14",
      "downloads": null,
      "homepage": ""
    }
  ]
}

宿主的校验很严格,而且是整份校验:任何一条目的包地址不是 https、 sha256 不是 64 位十六进制、版本号非法,都会让整次拉取失败。

pnpm verify:market        # 按宿主同一套规则校验产物索引

发布一个包

  1. 构建插件,产物里带上 manifest.json 与 icon.svg。
  2. 压缩成 <name>-<version>.zip,根目录就是 manifest 所在层级。
  3. 发布 —— sha256、条目、下载地址、图标地址都会自动生成。
  4. 签名 —— 公钥已配置的宿主不签名就不加载这份索引。
  5. 发布前跑一次校验,确认这份索引宿主会接受。
pnpm sync:market          # 从 market/ 下的 zip 重生成索引与站点目录

发布闸门

声明 runtime.kind = "process" 的包会在发布那一刻被直接拒掉, 构建失败退出。宿主在安装时也会拦,但那是消费点的兜底 —— 索引一旦发布出去就已经在网上,宿主拦下安装也撤不回那个条目。

签名

Ed25519。私钥只放在 CI secret TABTAB_MARKET_SIGNING_SEED, 公钥编译进宿主(INDEX_SIGNING_PUBKEY,用户可审计)。 签名覆盖索引里除 signature 字段以外的全部内容 —— 包括每一条的下载地址与 sha256,所以中途改一改包地址就会当场对不上。

签名必须排在 sync:market 之后:索引的生成者每次都会把signature 重置成空串(内容一改,旧签名本就作废),顺序反了等于没签。pnpm build 已把三步排好;本地没设私钥时会跳过并告警。

TABTAB_MARKET_SIGNING_SEED=<私钥> TABTAB_REQUIRE_SIGNING=1 pnpm sign:market
  • 私钥不走命令行。命令行参数在同一台机器上对所有进程可见 (ps、各进程的 cmdline),CI 的构建日志也常把它们原样记下 —— 所以发布一律从环境变量读。
  • 开发构建宽容。没有配置公钥时索引仍可拉取,但界面会明确告知 「该索引未经签名验证」,而不是静默当作可信内容展示。
  • release 构建强制。INDEX_SIGNING_PUBKEY 为空时 商城功能直接停用 —— 不是警告。横幅在真实使用里没人看, 「配没配公钥」若不影响任何行为,一个忘记配公钥的发布就过去了。
  • 验签钉在消费点。缓存对象与「它是可信的」之间没有绑定: 验签写在安装的那一刻,而不是「拉取时验过了就信」。

自建市场源

索引地址可在设置里覆盖(便于自建源)。有两条硬约束:

  • 索引地址必须显式走 https。本机自建源(http://localhost)放行。
  • 条目里的 url 与 icon 同样强制 https —— http 图标既是混合内容,也让索引作者能通过加载行为追踪谁在看哪个插件。

本地导入

带子进程的包、以及任何没进公开索引的包,走本地导入。 差别只有一处,但它必须被说清楚:本地包不经签名验证。 所以宿主的返回值里 signed 恒为 false,前端必须明示, 且装之前会先探查一遍权限、再明确告知「这个包会起子进程」。

当前官方市场

已上架 14 个包,索引更新于2026/10/14。 任何人都可以把这份目录复制走自己托管 —— 客户端不校验索引来源的主机名, 只认签名。