1229 lines
45 KiB
YAML
1229 lines
45 KiB
YAML
openapi: 3.1.0
|
|
info:
|
|
title: WebDrop API
|
|
version: 2.1.0
|
|
servers:
|
|
- url: /api/v1
|
|
|
|
tags:
|
|
- name: server
|
|
- name: channel
|
|
- name: seat
|
|
- name: frame
|
|
- name: item
|
|
- name: share
|
|
- name: account
|
|
|
|
paths:
|
|
/accounts/channels:
|
|
get:
|
|
tags: [account]
|
|
security: [{ accountBearer: [] }]
|
|
summary: 领取当前账号已加入频道的加密密钥包
|
|
responses:
|
|
'200':
|
|
description: 仅返回已加入频道,不占设备席位
|
|
content:
|
|
application/json:
|
|
schema: { type: array, items: { $ref: '#/components/schemas/AccountChannel' } }
|
|
'401': { $ref: '#/components/responses/Error' }
|
|
/accounts/devices:
|
|
get:
|
|
tags: [account]
|
|
security: [{ accountBearer: [] }]
|
|
summary: 读取当前账号的设备会话
|
|
responses:
|
|
'200':
|
|
description: 设备列表
|
|
content:
|
|
application/json:
|
|
schema: { type: array, items: { $ref: '#/components/schemas/AccountDevice' } }
|
|
'401': { $ref: '#/components/responses/Error' }
|
|
/accounts/devices/{id}:
|
|
parameters:
|
|
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
|
|
patch:
|
|
tags: [account]
|
|
security: [{ accountBearer: [] }]
|
|
summary: 重命名本账号设备
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [name]
|
|
properties: { name: { type: string, minLength: 1, maxLength: 80 } }
|
|
responses:
|
|
'204': { description: 已更新 }
|
|
'400': { $ref: '#/components/responses/Error' }
|
|
'401': { $ref: '#/components/responses/Error' }
|
|
delete:
|
|
tags: [account]
|
|
security: [{ accountBearer: [] }]
|
|
summary: 撤销该设备全部 sessions 与频道席位,保留账号成员资格
|
|
responses:
|
|
'204': { description: 已撤销 }
|
|
'401': { $ref: '#/components/responses/Error' }
|
|
/channels/{id}/key-package:
|
|
put:
|
|
tags: [channel]
|
|
security: [{ bearer: [] }]
|
|
summary: 频道主补齐给自己账号的加密密钥包
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ChannelId' }
|
|
- { $ref: '#/components/parameters/Role' }
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [payload]
|
|
properties: { payload: { type: string, minLength: 1, maxLength: 16384 } }
|
|
responses:
|
|
'204': { description: 已保存 }
|
|
'401': { $ref: '#/components/responses/Error' }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
/info:
|
|
get:
|
|
tags: [server]
|
|
security: []
|
|
summary: 服务端能力与限制
|
|
responses:
|
|
'200':
|
|
description: OK
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/ServerInfo' }
|
|
|
|
/accounts:
|
|
post:
|
|
tags: [account]
|
|
security: []
|
|
summary: 创建账号
|
|
description: 仅接收口令 verifier 和客户端加密保险库,不接收密码。
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AccountRegister' }
|
|
responses:
|
|
'204': { description: 已创建 }
|
|
'400': { $ref: '#/components/responses/Error' }
|
|
'409': { $ref: '#/components/responses/Error' }
|
|
'429': { $ref: '#/components/responses/Error' }
|
|
|
|
/accounts/salt:
|
|
post:
|
|
tags: [account]
|
|
security: []
|
|
summary: 查询账号口令派生 salt
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AccountSaltRequest' }
|
|
responses:
|
|
'200':
|
|
description: 客户端 PBKDF2 salt
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AccountSalt' }
|
|
'400': { $ref: '#/components/responses/Error' }
|
|
|
|
/accounts/session:
|
|
post:
|
|
tags: [account]
|
|
security: []
|
|
summary: 验证客户端派生凭据并创建 session
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AccountSessionRequest' }
|
|
responses:
|
|
'200':
|
|
description: 已登录
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AccountSession' }
|
|
'401': { $ref: '#/components/responses/Error' }
|
|
'400': { $ref: '#/components/responses/Error' }
|
|
delete:
|
|
tags: [account]
|
|
security: [{ accountBearer: [] }]
|
|
summary: 撤销当前账号 session
|
|
responses:
|
|
'204': { description: 已撤销 }
|
|
|
|
/accounts/vault:
|
|
get:
|
|
tags: [account]
|
|
security: [{ accountBearer: [] }]
|
|
summary: 读取客户端加密保险库
|
|
responses:
|
|
'200':
|
|
description: 当前保险库和 revision
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AccountVault' }
|
|
'401': { $ref: '#/components/responses/Error' }
|
|
put:
|
|
tags: [account]
|
|
security: [{ accountBearer: [] }]
|
|
summary: 以 revision 乐观并发更新保险库
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AccountVault' }
|
|
responses:
|
|
'200':
|
|
description: 已更新
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AccountVault' }
|
|
'401': { $ref: '#/components/responses/Error' }
|
|
'409': { $ref: '#/components/responses/Error' }
|
|
|
|
/accounts/users/{username}/key:
|
|
get:
|
|
tags: [account]
|
|
security: [{ accountBearer: [] }]
|
|
summary: 查询被邀请用户公钥
|
|
parameters:
|
|
- { name: username, in: path, required: true, schema: { type: string } }
|
|
responses:
|
|
'200':
|
|
description: 公钥
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/PublicKey' }
|
|
'404': { $ref: '#/components/responses/Error' }
|
|
/accounts/invitations:
|
|
get:
|
|
tags: [account]
|
|
security: [{ accountBearer: [] }]
|
|
summary: 当前账号收到的邀请
|
|
responses:
|
|
'200':
|
|
description: 邀请列表
|
|
content:
|
|
application/json:
|
|
schema: { type: array, items: { $ref: '#/components/schemas/ChannelInvitation' } }
|
|
/channels/{id}/members:
|
|
get:
|
|
tags: [channel]
|
|
security: [{ bearer: [] }]
|
|
summary: 读取频道内账号成员及角色
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ChannelId' }
|
|
- { $ref: '#/components/parameters/Role' }
|
|
- { $ref: '#/components/parameters/Endpoint' }
|
|
responses:
|
|
'200':
|
|
description: 成员列表
|
|
content:
|
|
application/json:
|
|
schema: { type: array, items: { $ref: '#/components/schemas/ChannelMember' } }
|
|
/channels/{id}/invitations:
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ChannelId' }
|
|
- { $ref: '#/components/parameters/RoleOwner' }
|
|
get:
|
|
tags: [channel]
|
|
security: [{ bearer: [], accountBearer: [] }]
|
|
summary: 频道主查询邀请
|
|
responses:
|
|
'200':
|
|
description: 邀请列表
|
|
content:
|
|
application/json:
|
|
schema: { type: array, items: { $ref: '#/components/schemas/ChannelInvitation' } }
|
|
post:
|
|
tags: [channel]
|
|
security: [{ bearer: [], accountBearer: [] }]
|
|
summary: 邀请已注册用户
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/InvitationRequest' }
|
|
responses:
|
|
'204': { description: 已邀请 }
|
|
'404': { $ref: '#/components/responses/Error' }
|
|
/channels/{id}/invitations/{username}:
|
|
delete:
|
|
tags: [channel]
|
|
security: [{ bearer: [], accountBearer: [] }]
|
|
summary: 撤销邀请和成员授权并移除其设备
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ChannelId' }
|
|
- { $ref: '#/components/parameters/RoleOwner' }
|
|
- { name: username, in: path, required: true, schema: { type: string } }
|
|
responses:
|
|
'204': { description: 已移除 }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
|
|
/channels:
|
|
post:
|
|
tags: [channel]
|
|
security: [{ accountBearer: [] }]
|
|
summary: 创建频道
|
|
description: |
|
|
服务端启用验证码时需要 `X-WD-Cap`(Cap 的 token,见 protocol §11),并需要终端头 `X-WD-Endpoint`。
|
|
创建者的账号自动成为该频道 owner,其终端自动入席。
|
|
parameters:
|
|
- { $ref: '#/components/parameters/Captcha' }
|
|
- { $ref: '#/components/parameters/Endpoint' }
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/CreateChannelRequest' }
|
|
responses:
|
|
'201':
|
|
description: 已创建
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/ChannelInfo' }
|
|
'400': { $ref: '#/components/responses/Error' }
|
|
'409': { $ref: '#/components/responses/Error' }
|
|
'428': { $ref: '#/components/responses/Error' }
|
|
'429': { $ref: '#/components/responses/Error' }
|
|
'503': { $ref: '#/components/responses/Error' }
|
|
|
|
/channels/{id}:
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ChannelId' }
|
|
get:
|
|
tags: [channel]
|
|
summary: 读取频道信息(不占席位)
|
|
description: 任意角色可调用。用于加入前预览席位占用和核对模式。
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/Role' }
|
|
responses:
|
|
'200':
|
|
description: OK
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/ChannelInfo' }
|
|
'401': { $ref: '#/components/responses/Error' }
|
|
'404': { $ref: '#/components/responses/Error' }
|
|
delete:
|
|
tags: [channel]
|
|
summary: 删除整个频道及全部密文(所有者)
|
|
description: 有效的 owner 凭据即可调用,不要求 owner 终端占用席位。
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/RoleOwner' }
|
|
responses:
|
|
'204': { description: 已删除 }
|
|
'401': { $ref: '#/components/responses/Error' }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
'404': { $ref: '#/components/responses/Error' }
|
|
|
|
/channels/{id}/settings:
|
|
patch:
|
|
tags: [channel]
|
|
summary: 修改设置(所有者)
|
|
description: |
|
|
字段均可选,只修改提供的字段。`ttl_seconds` 的变更从现在起算。
|
|
成功后向订阅者推送 `{"t":"settings"}`。有效的 owner 凭据即可调用,不要求 owner 终端占用席位。
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ChannelId' }
|
|
- { $ref: '#/components/parameters/RoleOwner' }
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/ChannelSettingsPatch' }
|
|
responses:
|
|
'200':
|
|
description: OK
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/ChannelInfo' }
|
|
'400': { $ref: '#/components/responses/Error' }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
|
|
/channels/{id}/seats:
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ChannelId' }
|
|
post:
|
|
tags: [seat]
|
|
summary: 加入频道(占用席位)
|
|
description: |
|
|
幂等:同一终端重复调用返回同一席位,不重复占用。
|
|
席位已满返回 403 `seats_full`。
|
|
读取频道信息和 owner 管理接口不占席位;频道内容、席位信息/设备名等其余操作要求终端已入席,否则 403 `not_joined`。
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/Role' }
|
|
- { $ref: '#/components/parameters/Endpoint' }
|
|
responses:
|
|
'200':
|
|
description: 已入席
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/SeatJoined' }
|
|
'401': { $ref: '#/components/responses/Error' }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
'404': { $ref: '#/components/responses/Error' }
|
|
get:
|
|
tags: [seat]
|
|
summary: 查看席位列表(所有者)
|
|
description: 有效的 owner 凭据即可调用,不要求 owner 终端占用席位。
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/RoleOwner' }
|
|
responses:
|
|
'200':
|
|
description: OK
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items: { $ref: '#/components/schemas/Seat' }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
|
|
/channels/{id}/seats/me:
|
|
put:
|
|
tags: [seat]
|
|
summary: 终端上传自己的加密设备名
|
|
description: 请求体是用 `K_enc` 加密后的设备名字节,≤256 B。服务端不解析。
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ChannelId' }
|
|
- { $ref: '#/components/parameters/Role' }
|
|
- { $ref: '#/components/parameters/Endpoint' }
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/octet-stream:
|
|
schema: { type: string, format: binary, maxLength: 256 }
|
|
responses:
|
|
'204': { description: 已保存 }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
'413': { $ref: '#/components/responses/Error' }
|
|
|
|
/channels/{id}/seats/{endpointHash}/label:
|
|
put:
|
|
tags: [seat]
|
|
summary: 所有者重命名设备
|
|
description: 请求体是设备名称(≤256 B);enc 模式以目标 endpoint hash 作为 AAD 加密,raw 模式为明文。
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ChannelId' }
|
|
- name: endpointHash
|
|
in: path
|
|
required: true
|
|
description: 终端标识,即 `base64url(SHA-256(S))`
|
|
schema: { $ref: '#/components/schemas/Base64Url' }
|
|
- { $ref: '#/components/parameters/RoleOwner' }
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/octet-stream:
|
|
schema: { type: string, format: binary, maxLength: 256 }
|
|
responses:
|
|
'204': { description: 名称已更新 }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
'404': { $ref: '#/components/responses/Error' }
|
|
'413': { $ref: '#/components/responses/Error' }
|
|
/channels/{id}/seats/{endpointHash}:
|
|
delete:
|
|
tags: [seat]
|
|
summary: 释放席位(所有者)
|
|
description: 有效的 owner 凭据即可调用,不要求 owner 终端占用席位。被释放终端后续请求返回 `not_joined`;向订阅者推送 `{"t":"seats"}`。
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ChannelId' }
|
|
- name: endpointHash
|
|
in: path
|
|
required: true
|
|
description: 终端标识,即 `base64url(SHA-256(S))`
|
|
schema: { $ref: '#/components/schemas/Base64Url' }
|
|
- { $ref: '#/components/parameters/RoleOwner' }
|
|
responses:
|
|
'204': { description: 已释放 }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
'404': { $ref: '#/components/responses/Error' }
|
|
|
|
/channels/{id}/frames:
|
|
post:
|
|
tags: [frame]
|
|
summary: 追加一帧
|
|
description: |
|
|
请求体是完整的帧封装(26 字节头 + 负载),格式见 `protocol.md` §4。
|
|
幂等键为 `(channel_id, item_id, frame_index)`。内容相同则 200 返回原 seq,内容不同则 409。
|
|
第 0 帧可带 `X-WD-Max-Downloads`;名称与线程字段统一在条目元数据中。
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ChannelId' }
|
|
- { $ref: '#/components/parameters/RoleWriter' }
|
|
- { $ref: '#/components/parameters/Endpoint' }
|
|
- name: X-WD-Max-Downloads
|
|
in: header
|
|
required: false
|
|
schema: { type: integer, minimum: 0 }
|
|
description: 条目的下载上限,0 为不限。只对 GATED 条目生效。
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/octet-stream:
|
|
schema: { type: string, format: binary }
|
|
responses:
|
|
'201':
|
|
description: 新追加
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AppendResult' }
|
|
'200':
|
|
description: 幂等重发
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AppendResult' }
|
|
'400': { $ref: '#/components/responses/Error' }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
'409': { $ref: '#/components/responses/Error' }
|
|
'413': { $ref: '#/components/responses/Error' }
|
|
'429': { $ref: '#/components/responses/Error' }
|
|
|
|
/channels/{id}/log:
|
|
get:
|
|
tags: [frame]
|
|
summary: 按 seq 拉取日志(长轮询、续传、全量同步)
|
|
description: |
|
|
返回日志记录的线格式拼接(`protocol.md` §8):每条 `seq(u64) ‖ kind(u8) ‖ body`。
|
|
`wait>0` 时无新记录会挂起,最长 25 秒,超时返回空体。
|
|
满足 `0 < from < oldest_seq` 时返回 410 并带 `oldest_seq`,客户端必须从 `from=0` 全量同步并丢弃本地已不存在的条目(不要从 `oldest_seq` 读起)。`from=0` 永远允许。
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ChannelId' }
|
|
- { $ref: '#/components/parameters/Role' }
|
|
- { $ref: '#/components/parameters/Endpoint' }
|
|
- name: from
|
|
in: query
|
|
required: false
|
|
schema: { type: integer, format: int64, minimum: 0, default: 0 }
|
|
- name: limit
|
|
in: query
|
|
required: false
|
|
schema: { type: integer, minimum: 1, maximum: 1000, default: 200 }
|
|
- name: wait
|
|
in: query
|
|
required: false
|
|
schema: { type: integer, minimum: 0, maximum: 25, default: 0 }
|
|
description: 长轮询等待秒数
|
|
responses:
|
|
'200':
|
|
description: 记录拼接,可为空
|
|
headers:
|
|
X-WD-Last-Seq:
|
|
description: 频道当前最新 seq
|
|
schema: { type: integer, format: int64 }
|
|
content:
|
|
application/octet-stream:
|
|
schema: { type: string, format: binary }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
'410':
|
|
description: 0 < from < oldest_seq,需要从 0 全量同步
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/GoneLog' }
|
|
|
|
/channels/{id}/ws:
|
|
get:
|
|
tags: [frame]
|
|
security: []
|
|
summary: WebSocket 订阅(降级链第一级)
|
|
description: |
|
|
升级为 WebSocket。浏览器不能设置头,凭据通过子协议传递:
|
|
`Sec-WebSocket-Protocol: wd.v1, wd.role.<role>, wd.auth.<base64url(令牌)>, wd.ep.<base64url(S)>`,
|
|
服务端回选 `wd.v1`。不要把令牌放在 URL 里。
|
|
|
|
**服务端 → 客户端**
|
|
- 二进制消息:一条日志记录(`seq ‖ kind ‖ body`)。
|
|
- 文本消息(JSON):`{"t":"hb","seq":N}` 心跳(每 20 秒);`{"t":"settings"}` 设置变更;`{"t":"seats"}` 席位变更。
|
|
|
|
客户端 45 秒收不到任何消息应判定死链,以指数退避(1~30 秒)加抖动重连,并带 `from=last_seq+1`。
|
|
上传不走 WebSocket,仍用 `POST /frames`。
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ChannelId' }
|
|
- name: from
|
|
in: query
|
|
required: false
|
|
schema: { type: integer, format: int64, minimum: 0, default: 0 }
|
|
responses:
|
|
'101': { description: 已升级 }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
'410':
|
|
description: 0 < from < oldest_seq,需要从 0 全量同步
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/GoneLog' }
|
|
|
|
/channels/{id}/events:
|
|
get:
|
|
tags: [frame]
|
|
summary: SSE 订阅(降级链第二级)
|
|
description: |
|
|
`text/event-stream`。每个事件的 `id:` 是 seq,`data:` 是 base64url 的日志记录;
|
|
控制消息与 WebSocket 的文本消息相同,事件名为 `control`。
|
|
`EventSource` 不能设置头,前端应使用 `fetch` 流式读取并带头部鉴权。
|
|
续传用 `from` 或 `Last-Event-ID`。
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ChannelId' }
|
|
- { $ref: '#/components/parameters/Role' }
|
|
- { $ref: '#/components/parameters/Endpoint' }
|
|
- name: from
|
|
in: query
|
|
required: false
|
|
schema: { type: integer, format: int64, minimum: 0, default: 0 }
|
|
responses:
|
|
'200':
|
|
description: 事件流
|
|
content:
|
|
text/event-stream:
|
|
schema: { type: string }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
'410':
|
|
description: 0 < from < oldest_seq,需要从 0 全量同步
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/GoneLog' }
|
|
|
|
/channels/{id}/items/{itemId}/frames:
|
|
get:
|
|
tags: [item]
|
|
summary: 拉取条目的帧(GATED 条目的取数方式)
|
|
description: |
|
|
返回封装拼接,每帧自带 `payload_len`,可流式解析。条目尚未 FINAL 时响应持续输出直到 FINAL。
|
|
`from=0` 计一次下载;`from>0` 只有同一终端此前对同一条目成功启动过 `from=0` 才能续传,否则返回 410 `gone`。
|
|
达到下载上限后返回 410 `gone`,5 分钟宽限期后删除密文,宽限期内已开始的拉取可继续续传。
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ChannelId' }
|
|
- { $ref: '#/components/parameters/ItemId' }
|
|
- { $ref: '#/components/parameters/Role' }
|
|
- { $ref: '#/components/parameters/Endpoint' }
|
|
- name: from
|
|
in: query
|
|
required: false
|
|
schema: { type: integer, format: int64, minimum: 0, default: 0 }
|
|
description: 起始 frame_index
|
|
responses:
|
|
'200':
|
|
description: 封装拼接
|
|
content:
|
|
application/octet-stream:
|
|
schema: { type: string, format: binary }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
'404': { $ref: '#/components/responses/Error' }
|
|
'410': { $ref: '#/components/responses/Error' }
|
|
|
|
/channels/{id}/items/{itemId}:
|
|
delete:
|
|
tags: [item]
|
|
summary: 删除条目
|
|
description: |
|
|
按频道的 `delete_policy` 授权:`owner_only` 仅所有者;`own` 时所有者或条目作者;`anyone` 时任意已入席终端。
|
|
成功后在日志追加 tombstone(`reason=0`)。
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ChannelId' }
|
|
- { $ref: '#/components/parameters/ItemId' }
|
|
- { $ref: '#/components/parameters/Role' }
|
|
- { $ref: '#/components/parameters/Endpoint' }
|
|
responses:
|
|
'204': { description: 已删除 }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
'404': { $ref: '#/components/responses/Error' }
|
|
|
|
/shares:
|
|
post:
|
|
tags: [share]
|
|
security: [{ accountBearer: [] }]
|
|
summary: 创建便捷分享草稿
|
|
description: 启用验证码时需携带 Cap token。创建者 endpoint 可选,创建者不计入访客数。
|
|
parameters:
|
|
- { $ref: '#/components/parameters/Captcha' }
|
|
- { $ref: '#/components/parameters/ShareEndpoint' }
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/CreateShareRequest' }
|
|
responses:
|
|
'201':
|
|
description: 已创建草稿
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/ShareInfo' }
|
|
'400': { $ref: '#/components/responses/Error' }
|
|
'409': { $ref: '#/components/responses/Error' }
|
|
'428': { $ref: '#/components/responses/Error' }
|
|
'429': { $ref: '#/components/responses/Error' }
|
|
'503': { $ref: '#/components/responses/Error' }
|
|
|
|
/shares/{id}:
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ShareId' }
|
|
get:
|
|
tags: [share]
|
|
summary: 读取分享信息
|
|
description: 已封存时使用 read;草稿仅 owner 可读,read 对草稿返回 not_found。
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/Role' }
|
|
responses:
|
|
'200':
|
|
description: 分享与条目下载状态
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/ShareInfo' }
|
|
'401': { $ref: '#/components/responses/Error' }
|
|
'404': { $ref: '#/components/responses/Error' }
|
|
delete:
|
|
tags: [share]
|
|
summary: 删除草稿
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/RoleOwner' }
|
|
responses:
|
|
'204': { description: 草稿已删除 }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
'404': { $ref: '#/components/responses/Error' }
|
|
'409': { $ref: '#/components/responses/Error' }
|
|
|
|
/shares/{id}/settings:
|
|
patch:
|
|
tags: [share]
|
|
summary: 修改草稿设置
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ShareId' }
|
|
- { $ref: '#/components/parameters/RoleOwner' }
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/ShareSettingsPatch' }
|
|
responses:
|
|
'200':
|
|
description: 设置已更新
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/ShareInfo' }
|
|
'400': { $ref: '#/components/responses/Error' }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
'409': { $ref: '#/components/responses/Error' }
|
|
|
|
/shares/{id}/frames:
|
|
post:
|
|
tags: [share]
|
|
summary: 向草稿追加加密帧
|
|
description: 不支持 SNAPSHOT。第 0 帧可用 X-WD-Max-Downloads 覆盖默认下载上限。响应的 seq 是 frame_index 确认值,不是日志游标。
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ShareId' }
|
|
- { $ref: '#/components/parameters/RoleOwner' }
|
|
- name: X-WD-Max-Downloads
|
|
in: header
|
|
required: false
|
|
schema: { type: integer, format: int64, minimum: 0 }
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/octet-stream:
|
|
schema: { type: string, format: binary }
|
|
responses:
|
|
'201':
|
|
description: 新帧已追加
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AppendResult' }
|
|
'200':
|
|
description: 幂等重发
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AppendResult' }
|
|
'400': { $ref: '#/components/responses/Error' }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
'409': { $ref: '#/components/responses/Error' }
|
|
'413': { $ref: '#/components/responses/Error' }
|
|
|
|
/shares/{id}/items/{itemId}:
|
|
delete:
|
|
tags: [share]
|
|
summary: 从草稿移除条目
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ShareId' }
|
|
- { $ref: '#/components/parameters/ItemId' }
|
|
- { $ref: '#/components/parameters/RoleOwner' }
|
|
responses:
|
|
'204': { description: 条目已删除 }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
'404': { $ref: '#/components/responses/Error' }
|
|
'409': { $ref: '#/components/responses/Error' }
|
|
|
|
/shares/{id}/finish:
|
|
post:
|
|
tags: [share]
|
|
summary: 封存分享
|
|
description: 验证 manifest 是单帧非 GATED 完整条目,且草稿内所有条目完整;成功后永久禁用写权限。
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ShareId' }
|
|
- { $ref: '#/components/parameters/RoleOwner' }
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/FinishShareRequest' }
|
|
responses:
|
|
'200':
|
|
description: 分享已封存
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/ShareInfo' }
|
|
'400': { $ref: '#/components/responses/Error' }
|
|
'409': { $ref: '#/components/responses/Error' }
|
|
|
|
/shares/{id}/manifest:
|
|
get:
|
|
tags: [share]
|
|
summary: 获取加密清单帧
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ShareId' }
|
|
- { $ref: '#/components/parameters/Role' }
|
|
- { $ref: '#/components/parameters/Endpoint' }
|
|
responses:
|
|
'200':
|
|
description: 完整封装的二进制清单
|
|
content:
|
|
application/octet-stream:
|
|
schema: { type: string, format: binary }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
'404': { $ref: '#/components/responses/Error' }
|
|
'410': { $ref: '#/components/responses/Error' }
|
|
|
|
/shares/{id}/items/{itemId}/frames:
|
|
get:
|
|
tags: [share]
|
|
summary: 下载封存分享中的文件帧
|
|
description: from=0 计一次下载;from>0 只有同一 X-WD-Endpoint 此前对同一文件成功启动过 from=0 才能续传,否则返回 410 gone 且不登记访客。首次成功读取会登记访客终端。
|
|
security: [{ bearer: [] }]
|
|
parameters:
|
|
- { $ref: '#/components/parameters/ShareId' }
|
|
- { $ref: '#/components/parameters/ItemId' }
|
|
- { $ref: '#/components/parameters/Role' }
|
|
- { $ref: '#/components/parameters/Endpoint' }
|
|
- name: from
|
|
in: query
|
|
required: false
|
|
schema: { type: integer, format: int64, minimum: 0, default: 0 }
|
|
responses:
|
|
'200':
|
|
description: 连续封装帧拼接
|
|
content:
|
|
application/octet-stream:
|
|
schema: { type: string, format: binary }
|
|
'403': { $ref: '#/components/responses/Error' }
|
|
'404': { $ref: '#/components/responses/Error' }
|
|
'410': { $ref: '#/components/responses/Error' }
|
|
|
|
components:
|
|
securitySchemes:
|
|
accountBearer:
|
|
type: apiKey
|
|
in: header
|
|
name: X-WD-Account
|
|
description: WebDrop 账号登录后颁发的随机 session token。
|
|
bearer:
|
|
type: http
|
|
scheme: bearer
|
|
description: |
|
|
令牌的 base64url(`T_read` / `T_write` / `T_owner`),与 `X-WD-Role` 配套。
|
|
raw 模式的读请求不需要令牌。
|
|
|
|
parameters:
|
|
ChannelId:
|
|
name: id
|
|
in: path
|
|
required: true
|
|
description: 频道 id,16 字节 base64url
|
|
schema: { $ref: '#/components/schemas/Base64Url' }
|
|
ShareId:
|
|
name: id
|
|
in: path
|
|
required: true
|
|
description: 便捷分享 id,16 字节 base64url
|
|
schema: { $ref: '#/components/schemas/Base64Url' }
|
|
ItemId:
|
|
name: itemId
|
|
in: path
|
|
required: true
|
|
description: 条目 id,16 字节 base64url
|
|
schema: { $ref: '#/components/schemas/Base64Url' }
|
|
Role:
|
|
name: X-WD-Role
|
|
in: header
|
|
required: true
|
|
description: 令牌所属角色
|
|
schema: { type: string, enum: [read, write, owner] }
|
|
RoleWriter:
|
|
name: X-WD-Role
|
|
in: header
|
|
required: true
|
|
schema: { type: string, enum: [write, owner] }
|
|
RoleOwner:
|
|
name: X-WD-Role
|
|
in: header
|
|
required: true
|
|
schema: { type: string, enum: [owner] }
|
|
Endpoint:
|
|
name: X-WD-Endpoint
|
|
in: header
|
|
required: true
|
|
description: 终端密钥 `S` 的 base64url。每个频道每个终端各生成一个,仅存本地。
|
|
schema: { $ref: '#/components/schemas/Base64Url' }
|
|
ShareEndpoint:
|
|
name: X-WD-Endpoint
|
|
in: header
|
|
required: false
|
|
description: 创建者终端密钥 S 的 base64url,可选,不占访客名额。
|
|
schema: { $ref: '#/components/schemas/Base64Url' }
|
|
Captcha:
|
|
name: X-WD-Cap
|
|
in: header
|
|
required: false
|
|
description: Cap 验证码 token。服务端 `/info` 的 `captcha.enabled` 为 true 时必填;token 一次有效。
|
|
schema: { type: string, maxLength: 2048 }
|
|
|
|
responses:
|
|
Error:
|
|
description: 错误。响应体不回显任何用户提交的内容。
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/Error' }
|
|
|
|
schemas:
|
|
AccountChannel:
|
|
type: object
|
|
required: [channel_id, role, payload, created_at]
|
|
properties:
|
|
channel_id: { $ref: '#/components/schemas/Base64Url' }
|
|
role: { type: string, enum: [owner, member] }
|
|
payload: { type: string, minLength: 1, maxLength: 16384 }
|
|
created_at: { type: integer, format: int64 }
|
|
AccountDevice:
|
|
type: object
|
|
required: [id, name, created_at, last_seen, current]
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
name: { type: string, minLength: 1, maxLength: 80 }
|
|
created_at: { type: integer, format: int64 }
|
|
last_seen: { type: integer, format: int64 }
|
|
current: { type: boolean }
|
|
PublicKey:
|
|
type: object
|
|
required: [public_key]
|
|
properties:
|
|
public_key: { type: string, maxLength: 1024 }
|
|
ChannelInvitation:
|
|
type: object
|
|
required: [channel_id, username, payload, created_at]
|
|
properties:
|
|
channel_id: { $ref: '#/components/schemas/Base64Url' }
|
|
username: { type: string }
|
|
payload: { type: string, maxLength: 16384, description: 客户端加密的成员链接 }
|
|
created_at: { type: integer, format: int64 }
|
|
InvitationRequest:
|
|
type: object
|
|
required: [username, payload]
|
|
properties:
|
|
username: { type: string }
|
|
payload: { type: string, maxLength: 16384 }
|
|
ChannelMember:
|
|
type: object
|
|
required: [username, role, joined_at]
|
|
properties:
|
|
username: { type: string }
|
|
role: { type: string, enum: [owner, member] }
|
|
joined_at: { type: integer, format: int64 }
|
|
AccountRegister:
|
|
type: object
|
|
required: [username, salt, verifier, vault, public_key]
|
|
properties:
|
|
username: { type: string, minLength: 3, maxLength: 32, pattern: '^[A-Za-z0-9_-]+$' }
|
|
salt: { type: string, contentEncoding: base64, minLength: 24, maxLength: 24 }
|
|
verifier: { type: string, contentEncoding: base64, minLength: 44, maxLength: 44 }
|
|
vault: { type: string, contentEncoding: base64, maxLength: 1733336 }
|
|
public_key: { type: string, maxLength: 1024, description: P-256 ECDH 公钥 JWK 的 JSON 字符串 }
|
|
AccountSaltRequest:
|
|
type: object
|
|
required: [username]
|
|
properties:
|
|
username: { type: string, minLength: 3, maxLength: 32 }
|
|
AccountSalt:
|
|
type: object
|
|
required: [salt]
|
|
properties:
|
|
salt: { type: string, contentEncoding: base64 }
|
|
AccountSessionRequest:
|
|
type: object
|
|
required: [username, verifier]
|
|
properties:
|
|
device_id: { type: string, format: uuid }
|
|
device_name: { type: string, minLength: 1, maxLength: 80 }
|
|
username: { type: string }
|
|
verifier: { type: string, contentEncoding: base64 }
|
|
AccountSession:
|
|
type: object
|
|
required: [username, token]
|
|
properties:
|
|
username: { type: string }
|
|
token: { type: string, contentEncoding: base64 }
|
|
AccountVault:
|
|
type: object
|
|
required: [revision, vault]
|
|
properties:
|
|
revision: { type: integer, format: int64, minimum: 0 }
|
|
vault: { type: string, contentEncoding: base64, maxLength: 1733336 }
|
|
Base64Url:
|
|
type: string
|
|
pattern: '^[A-Za-z0-9_-]+$'
|
|
description: base64url,无填充
|
|
|
|
ErrorCode:
|
|
type: string
|
|
enum:
|
|
- bad_request
|
|
- unauthorized
|
|
- forbidden
|
|
- not_joined
|
|
- seats_full
|
|
- mode_mismatch
|
|
- not_found
|
|
- conflict
|
|
- gone
|
|
- too_large
|
|
- captcha_required
|
|
- captcha_unavailable
|
|
- rate_limited
|
|
- visitors_full
|
|
- sealed
|
|
- account_required
|
|
- account_invalid
|
|
- invitation_required
|
|
- channel_password_required
|
|
- channel_password_invalid
|
|
- username_unavailable
|
|
- vault_conflict
|
|
|
|
Error:
|
|
type: object
|
|
required: [error]
|
|
properties:
|
|
error: { $ref: '#/components/schemas/ErrorCode' }
|
|
|
|
GoneLog:
|
|
type: object
|
|
required: [error, oldest_seq]
|
|
properties:
|
|
error: { type: string, const: gone }
|
|
oldest_seq: { type: integer, format: int64, description: "清理水位 + 1;0 < from < oldest_seq 的续传需要从 0 全量同步" }
|
|
|
|
ServerInfo:
|
|
type: object
|
|
required: [protocol, contract_version, limits, captcha, transports]
|
|
properties:
|
|
protocol: { type: integer, const: 1 }
|
|
contract_version: { type: string, example: 2.0.0, description: API 契约的 SemVer。破坏性变更递增主版本。 }
|
|
captcha:
|
|
type: object
|
|
required: [provider, enabled]
|
|
properties:
|
|
provider: { type: string, enum: [none, cap] }
|
|
enabled: { type: boolean }
|
|
api_endpoint:
|
|
type: string
|
|
description: Cap widget 要连接的地址(data-cap-api-endpoint),仅在启用时出现
|
|
transports:
|
|
type: object
|
|
properties:
|
|
websocket: { type: boolean }
|
|
sse: { type: boolean }
|
|
http3: { type: boolean }
|
|
limits:
|
|
type: object
|
|
properties:
|
|
max_payload_bytes: { type: integer }
|
|
max_frames_per_item: { type: integer }
|
|
max_channel_bytes: { type: integer, format: int64 }
|
|
max_seats: { type: integer }
|
|
max_ttl_seconds: { type: integer }
|
|
|
|
ChannelMode:
|
|
type: string
|
|
enum: [enc, raw]
|
|
|
|
DeletePolicy:
|
|
type: string
|
|
enum: [owner_only, own, anyone]
|
|
|
|
ChannelSettings:
|
|
type: object
|
|
required: [seats, members_can_upload, delete_policy, ttl_seconds, max_items, file_max_downloads, ui_restrict_copy_download]
|
|
properties:
|
|
join_policy: { type: string, enum: [open, invite], default: open }
|
|
allow_guests: { type: boolean, default: true }
|
|
name: { type: string, maxLength: 48, description: "共享频道名,所有成员和服务端可见" }
|
|
seats: { type: integer, minimum: 1, default: 2 }
|
|
members_can_upload: { type: boolean, default: true }
|
|
delete_policy: { $ref: '#/components/schemas/DeletePolicy' }
|
|
ttl_seconds: { type: integer, minimum: 60, maximum: 604800, default: 86400 }
|
|
max_items: { type: integer, minimum: 1, default: 1000 }
|
|
file_max_downloads: { type: integer, minimum: 0, default: 10, description: "0 为不限,仅对 GATED 条目生效" }
|
|
ui_restrict_copy_download: { type: boolean, default: false, description: "仅供前端展示,服务端不强制" }
|
|
|
|
ChannelSettingsPatch:
|
|
type: object
|
|
description: ChannelSettings 的全部字段均可选
|
|
properties:
|
|
join_policy: { type: string, enum: [open, invite] }
|
|
allow_guests: { type: boolean }
|
|
join_password: { type: string, maxLength: 256, description: 至少 8 字符;空字符串清除;省略保持原值 }
|
|
name: { type: string, maxLength: 48, description: "更新共享频道名;空字符串清除名称" }
|
|
seats: { type: integer, minimum: 1 }
|
|
members_can_upload: { type: boolean }
|
|
delete_policy: { $ref: '#/components/schemas/DeletePolicy' }
|
|
ttl_seconds: { type: integer, minimum: 60, maximum: 604800 }
|
|
max_items: { type: integer, minimum: 1 }
|
|
file_max_downloads: { type: integer, minimum: 0 }
|
|
ui_restrict_copy_download: { type: boolean }
|
|
|
|
CreateChannelRequest:
|
|
type: object
|
|
required: [id, mode, settings, owner_hash]
|
|
properties:
|
|
key_package: { type: string, minLength: 1, maxLength: 16384, description: 用创建者账号公钥封装的 owner 凭据 }
|
|
join_password: { type: string, minLength: 8, maxLength: 256 }
|
|
id: { $ref: '#/components/schemas/Base64Url' }
|
|
mode: { $ref: '#/components/schemas/ChannelMode' }
|
|
settings: { $ref: '#/components/schemas/ChannelSettings' }
|
|
read_hash:
|
|
$ref: '#/components/schemas/Base64Url'
|
|
description: SHA-256(T_read),enc 模式必填,raw 模式省略
|
|
write_hash:
|
|
$ref: '#/components/schemas/Base64Url'
|
|
description: SHA-256(T_write 或 W),仅在成员可上传时提供
|
|
owner_hash:
|
|
$ref: '#/components/schemas/Base64Url'
|
|
description: SHA-256(T_owner)
|
|
|
|
ShareSettings:
|
|
type: object
|
|
required: [ttl_seconds, max_visitors, file_max_downloads]
|
|
properties:
|
|
ttl_seconds: { type: integer, minimum: 60, maximum: 604800, default: 86400 }
|
|
max_visitors: { type: integer, minimum: 0, maximum: 10000, default: 0, description: 0 为不限 }
|
|
file_max_downloads: { type: integer, minimum: 0, default: 10, description: 0 为不限 }
|
|
|
|
ShareSettingsPatch:
|
|
type: object
|
|
description: ShareSettings 的字段均可选。
|
|
properties:
|
|
ttl_seconds: { type: integer, minimum: 60, maximum: 604800 }
|
|
max_visitors: { type: integer, minimum: 0, maximum: 10000 }
|
|
file_max_downloads: { type: integer, minimum: 0 }
|
|
|
|
CreateShareRequest:
|
|
type: object
|
|
required: [id, read_hash, owner_hash]
|
|
properties:
|
|
id: { $ref: '#/components/schemas/Base64Url' }
|
|
read_hash: { $ref: '#/components/schemas/Base64Url', description: SHA-256(T_read) }
|
|
owner_hash: { $ref: '#/components/schemas/Base64Url', description: SHA-256(T_owner) }
|
|
settings: { $ref: '#/components/schemas/ShareSettings' }
|
|
|
|
FinishShareRequest:
|
|
type: object
|
|
required: [manifest_item_id]
|
|
properties:
|
|
manifest_item_id: { $ref: '#/components/schemas/Base64Url' }
|
|
|
|
ShareItemInfo:
|
|
type: object
|
|
required: [item_id, bytes, frames, max_downloads, downloads_left]
|
|
properties:
|
|
complete: { type: boolean }
|
|
item_id: { $ref: '#/components/schemas/Base64Url' }
|
|
bytes: { type: integer, format: int64 }
|
|
frames: { type: integer }
|
|
max_downloads: { type: integer, format: int64, description: 0 为不限 }
|
|
downloads_left: { type: [integer, 'null'], format: int64, description: "null 表示不限" }
|
|
|
|
ShareInfo:
|
|
type: object
|
|
required: [id, state, expires_at, created_at, settings, items]
|
|
properties:
|
|
id: { $ref: '#/components/schemas/Base64Url' }
|
|
state: { type: string, enum: [draft, sealed] }
|
|
expires_at: { type: integer, format: int64 }
|
|
created_at: { type: integer, format: int64 }
|
|
sealed_at: { type: [integer, 'null'], format: int64 }
|
|
manifest_item_id: { type: [string, 'null'], description: 清单条目 id }
|
|
settings: { $ref: '#/components/schemas/ShareSettings' }
|
|
items:
|
|
type: array
|
|
items: { $ref: '#/components/schemas/ShareItemInfo' }
|
|
|
|
ChannelInfo:
|
|
type: object
|
|
required: [id, mode, settings, seats_used, oldest_seq, last_seq, created_at, expires_at]
|
|
properties:
|
|
password_required: { type: boolean }
|
|
creator_account: { type: string }
|
|
id: { $ref: '#/components/schemas/Base64Url' }
|
|
mode: { $ref: '#/components/schemas/ChannelMode' }
|
|
settings: { $ref: '#/components/schemas/ChannelSettings' }
|
|
seats_used: { type: integer }
|
|
oldest_seq: { type: integer, format: int64 }
|
|
last_seq: { type: integer, format: int64 }
|
|
created_at: { type: integer, format: int64 }
|
|
expires_at: { type: integer, format: int64 }
|
|
|
|
SeatJoined:
|
|
type: object
|
|
required: [endpoint_hash, joined_at, seats_used, seats_max]
|
|
properties:
|
|
endpoint_hash: { $ref: '#/components/schemas/Base64Url' }
|
|
joined_at: { type: integer, format: int64 }
|
|
seats_used: { type: integer }
|
|
seats_max: { type: integer }
|
|
|
|
Seat:
|
|
type: object
|
|
required: [endpoint_hash, joined_at]
|
|
properties:
|
|
endpoint_hash: { $ref: '#/components/schemas/Base64Url' }
|
|
joined_at: { type: integer, format: int64 }
|
|
label:
|
|
type: [string, 'null']
|
|
description: 加密的设备名(base64url),用 K_enc 解密;未设置为 null
|
|
|
|
AppendResult:
|
|
type: object
|
|
required: [seq]
|
|
properties:
|
|
seq: { type: integer, format: int64 }
|