chore(repo): initialize WebDrop monorepo

Signed-off-by: JustMe <mr.toing@outlook.com>
This commit is contained in:
justme committed 2026-10-02 00:59:22 +08:00
commit 53768a195d
39 files changed
+9063

No files matched your search

+860
View File
@@ -0,0 +1,860 @@
openapi: 3.1.0
info:
title: WebDrop API
version: 1.0.0
servers:
- url: /api/v1
tags:
- name: server
- name: channel
- name: seat
- name: frame
- name: item
- name: share
paths:
/info:
get:
tags: [server]
security: []
summary: 服务端能力与限制
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/ServerInfo' }
/channels:
post:
tags: [channel]
security: []
summary: 创建频道
description: |
服务端启用验证码时需要 `X-WD-Cap`(Cap 的 token,见 protocol §11),并需要终端头 `X-WD-Endpoint`。
创建者的终端自动入席,占一个席位。
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: 删除整个频道及全部密文(所有者)
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"}`。
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`。
除本接口和「读取频道信息」外,其余频道接口都要求终端已入席,否则 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: 查看席位列表(所有者)
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}:
delete:
tags: [seat]
summary: 释放席位(所有者)
description: 被释放的终端后续请求返回 `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` 和(raw 模式)`X-WD-Label`。
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 条目生效。
- name: X-WD-Label
in: header
required: false
schema: { type: string, maxLength: 255 }
description: raw 模式的条目标签,明文。
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` 视为续传,不计数。
达到下载上限后返回 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: []
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 为续传。首次成功读取会登记访客终端。
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:
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:
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
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, limits, captcha, transports]
properties:
protocol: { type: integer, const: 1 }
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:
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:
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:
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:
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:
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 }