feat(api)!: fold devices into users and report member presence
Contract 7.0.0 (breaking). Devices become an attribute of the account;
members are managed per user, and only unauthenticated guests keep a
per-terminal registration.
- rename seats -> max_members, seats_used -> members_used,
seats_full -> members_full, SeatJoined -> JoinResult, the `seats`
stream control message -> `members`, and POST /channels/{id}/seats ->
POST /channels/{id}/join (stored channel settings are migrated on start)
- drop per-terminal encrypted device labels and the seat list/release
endpoints; add GET/DELETE /channels/{id}/guests for guest terminals
- authenticate account members by account session only: the terminal key
is optional for logged-in accounts, no seat row is written for them,
and gated-download grants are counted per user (per terminal for guests)
- GET /channels/{id}/members now returns online, devices and
online_devices per account; presence is tracked in memory from active
WebSocket/SSE subscriptions and is not pushed over the stream
- owner invitation list no longer returns accounts that already joined
This commit is contained in:
1 parent
0fd2a269d9
commit
d946fc5e8a
35 files changed
+517
-490
No files matched your search
+45
-77
@@ -1,14 +1,15 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: WebDrop API
|
||||
version: 6.0.0
|
||||
version: 7.0.0
|
||||
servers:
|
||||
- url: /api/v1
|
||||
|
||||
tags:
|
||||
- name: server
|
||||
- name: channel
|
||||
- name: seat
|
||||
- name: join
|
||||
- name: guest
|
||||
- name: frame
|
||||
- name: item
|
||||
- name: share
|
||||
@@ -404,7 +405,7 @@ paths:
|
||||
summary: 领取当前账号已加入频道的加密密钥包
|
||||
responses:
|
||||
'200':
|
||||
description: 仅返回已加入频道,不占设备席位
|
||||
description: 仅返回已加入频道
|
||||
content:
|
||||
application/json:
|
||||
schema: { type: array, items: { $ref: '#/components/schemas/AccountChannel' } }
|
||||
@@ -443,7 +444,7 @@ paths:
|
||||
delete:
|
||||
tags: [account]
|
||||
security: [{ accountBearer: [] }]
|
||||
summary: 撤销该设备全部 sessions 与频道席位,保留账号成员资格
|
||||
summary: 撤销该设备的全部 sessions,保留账号成员资格
|
||||
responses:
|
||||
'204': { description: 已撤销 }
|
||||
'401': { $ref: '#/components/responses/Error' }
|
||||
@@ -702,7 +703,7 @@ paths:
|
||||
get:
|
||||
tags: [channel]
|
||||
summary: 读取频道信息(不占成员名额)
|
||||
description: 任意角色可调用。`seats_used` 表示唯一账号成员数加匿名访客终端数,不是设备数。
|
||||
description: 任意角色可调用。`members_used` 表示唯一账号成员数加匿名访客终端数,不是设备数。
|
||||
security: [{ bearer: [] }]
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/Role' }
|
||||
@@ -717,7 +718,7 @@ paths:
|
||||
delete:
|
||||
tags: [channel]
|
||||
summary: 删除整个频道及全部密文(所有者)
|
||||
description: 有效的 owner 凭据即可调用,不要求 owner 终端占用席位。
|
||||
description: 有效的 owner 凭据即可调用,不要求 owner 终端登记。
|
||||
security: [{ bearer: [] }]
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/RoleOwner' }
|
||||
@@ -733,7 +734,7 @@ paths:
|
||||
summary: 修改设置(所有者)
|
||||
description: |
|
||||
字段均可选,只修改提供的字段。`ttl_seconds` 的变更从现在起算。
|
||||
成功后向订阅者推送 `{"t":"settings"}`。有效的 owner 凭据即可调用,不要求 owner 终端占用席位。
|
||||
成功后向订阅者推送 `{"t":"settings"}`。有效的 owner 凭据即可调用,不要求 owner 终端登记。
|
||||
security: [{ bearer: [] }]
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/ChannelId' }
|
||||
@@ -752,35 +753,46 @@ paths:
|
||||
'400': { $ref: '#/components/responses/Error' }
|
||||
'403': { $ref: '#/components/responses/Error' }
|
||||
|
||||
/channels/{id}/seats:
|
||||
/channels/{id}/join:
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/ChannelId' }
|
||||
post:
|
||||
tags: [seat]
|
||||
summary: 登记当前设备并加入频道
|
||||
tags: [join]
|
||||
summary: 加入频道
|
||||
description: |
|
||||
幂等:同一终端重复调用返回同一设备席位,不重复登记。
|
||||
同一账号的设备不限量,账号首次加入时才占一个成员名额;成员名额已满返回 403 `seats_full`。
|
||||
未登录访客每个终端单独计一个成员名额。读取频道信息和 owner 管理接口不占成员名额;频道内容、设备席位/设备名等操作要求终端已入席,否则 403 `not_joined`。
|
||||
幂等。登录账号只登记成员关系:同一账号的多台设备不增加人数,也不登记终端,此后仅凭账号会话鉴权(`X-WD-Endpoint` 可省略)。
|
||||
未登录访客登记一个终端(`X-WD-Endpoint` 必填),每个终端单独计一个成员名额。成员名额已满返回 403 `members_full`。
|
||||
读取频道信息和 owner 管理接口不占名额;访客访问频道内容前必须已加入,否则 403 `not_joined`。
|
||||
security: [{ bearer: [] }]
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/Role' }
|
||||
- { $ref: '#/components/parameters/Endpoint' }
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
password: { type: string, minLength: 8, maxLength: 256 }
|
||||
responses:
|
||||
'200':
|
||||
description: 已入席
|
||||
description: 已加入
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/SeatJoined' }
|
||||
schema: { $ref: '#/components/schemas/JoinResult' }
|
||||
'401': { $ref: '#/components/responses/Error' }
|
||||
'403': { $ref: '#/components/responses/Error' }
|
||||
'404': { $ref: '#/components/responses/Error' }
|
||||
|
||||
/channels/{id}/guests:
|
||||
get:
|
||||
tags: [seat]
|
||||
summary: 查看已加入设备列表(所有者)
|
||||
description: 有效的 owner 凭据即可调用,不要求 owner 终端占用席位。
|
||||
tags: [guest]
|
||||
summary: 查看访客(所有者)
|
||||
description: 只列未登录访客的终端。账号成员按用户管理(`/members`、`/invitations`),其设备不对外列出。
|
||||
security: [{ bearer: [] }]
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/ChannelId' }
|
||||
- { $ref: '#/components/parameters/RoleOwner' }
|
||||
responses:
|
||||
'200':
|
||||
@@ -789,69 +801,25 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items: { $ref: '#/components/schemas/Seat' }
|
||||
items: { $ref: '#/components/schemas/Guest' }
|
||||
'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}:
|
||||
/channels/{id}/guests/{endpointHash}:
|
||||
delete:
|
||||
tags: [seat]
|
||||
summary: 撤销单台设备(所有者)
|
||||
description: 有效的 owner 凭据即可调用,不要求 owner 终端占用设备席位。被释放终端后续请求返回 `not_joined`;账号成员身份及人数名额保留;向订阅者推送 `{"t":"seats"}`。
|
||||
tags: [guest]
|
||||
summary: 移除访客(所有者)
|
||||
description: 移除该访客终端并禁止它再加入;被移除终端后续请求返回 `not_joined`。账号成员改用 `DELETE /invitations/{username}`。
|
||||
security: [{ bearer: [] }]
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/ChannelId' }
|
||||
- name: endpointHash
|
||||
in: path
|
||||
required: true
|
||||
description: 终端标识,即 `base64url(SHA-256(S))`
|
||||
description: 访客终端标识,即 `base64url(SHA-256(S))`
|
||||
schema: { $ref: '#/components/schemas/Base64Url' }
|
||||
- { $ref: '#/components/parameters/RoleOwner' }
|
||||
responses:
|
||||
'204': { description: 已释放 }
|
||||
'204': { description: 已移除 }
|
||||
'403': { $ref: '#/components/responses/Error' }
|
||||
'404': { $ref: '#/components/responses/Error' }
|
||||
|
||||
@@ -950,7 +918,7 @@ paths:
|
||||
|
||||
**服务端 → 客户端**
|
||||
- 二进制消息:一条日志记录(`seq ‖ kind ‖ body`)。
|
||||
- 文本消息(JSON):`{"t":"hb","seq":N}` 心跳(每 20 秒);`{"t":"settings"}` 设置变更;`{"t":"seats"}` 席位变更。
|
||||
- 文本消息(JSON):`{"t":"hb","seq":N}` 心跳(每 20 秒);`{"t":"settings"}` 设置变更;`{"t":"members"}` 成员/访客变更。
|
||||
|
||||
客户端 45 秒收不到任何消息应判定死链,以指数退避(1~30 秒)加抖动重连,并带 `from=last_seq+1`。
|
||||
上传不走 WebSocket,仍用 `POST /frames`。
|
||||
@@ -1294,8 +1262,8 @@ components:
|
||||
Endpoint:
|
||||
name: X-WD-Endpoint
|
||||
in: header
|
||||
required: true
|
||||
description: 终端密钥 `S` 的 base64url。每个频道每个终端各生成一个,仅存本地。
|
||||
required: false
|
||||
description: 终端密钥 `S` 的 base64url,仅存本地。只有未登录访客必填;登录账号以 `X-WD-Account` 会话鉴权,可省略。
|
||||
schema: { $ref: '#/components/schemas/Base64Url' }
|
||||
ShareEndpoint:
|
||||
name: X-WD-Endpoint
|
||||
@@ -1368,7 +1336,7 @@ components:
|
||||
- unauthorized
|
||||
- forbidden
|
||||
- not_joined
|
||||
- seats_full
|
||||
- members_full
|
||||
- mode_mismatch
|
||||
- not_found
|
||||
- conflict
|
||||
@@ -1439,11 +1407,11 @@ components:
|
||||
ChannelInfo:
|
||||
$ref: '../contracts/api.schemas.json#/$defs/ChannelInfo'
|
||||
|
||||
SeatJoined:
|
||||
$ref: '../contracts/api.schemas.json#/$defs/SeatJoined'
|
||||
JoinResult:
|
||||
$ref: '../contracts/api.schemas.json#/$defs/JoinResult'
|
||||
|
||||
Seat:
|
||||
$ref: '../contracts/api.schemas.json#/$defs/SeatInfo'
|
||||
Guest:
|
||||
$ref: '../contracts/api.schemas.json#/$defs/GuestInfo'
|
||||
|
||||
AppendResult:
|
||||
$ref: '../contracts/api.schemas.json#/$defs/AppendResult'
|
||||
Reference in new issue
Block a user