# 服务端 API 文档 > 本文档详细介绍 Tinode 服务端 API,包括消息格式、协议细节和使用方法。 --- ## 目录 - [工作原理](#工作原理) - [通用说明](#通用说明) - [连接服务器](#连接服务器) - [gRPC](#grpc) - [WebSocket](#websocket) - [长轮询](#长轮询) - [带外大文件](#带外大文件) - [用户](#用户) - [认证](#认证) - [凭证验证](#凭证验证) - [访问控制](#访问控制) - [话题](#话题) - [me 话题](#me-话题) - [fnd 话题与标签](#fnd-话题与标签) - [点对点话题](#点对点话题) - [群组话题](#群组话题) - [消息](#消息) - [客户端到服务端消息](#客户端到服务端消息) - [服务端到客户端消息](#服务端到客户端消息) - [内容格式](#内容格式) - [推送通知](#推送通知) - [视频通话](#视频通话) --- ## 工作原理 Tinode 是一个 IM 路由器和存储系统。概念上它松散地遵循 [发布-订阅](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) 模式。 服务器连接会话、用户和话题: - **会话(Session)**:客户端应用与服务器之间的网络连接 - **用户(User)**:通过会话连接到服务器的人 - **话题(Topic)**:在会话之间路由内容的命名通信通道 用户和话题被分配唯一 ID: - **用户 ID**:字符串,格式为 `usr` 前缀 + base64-URL 编码的伪随机 64 位数字,如 `usr2il9suCbuko` - **话题 ID**:见下文话题类型说明 客户端(如移动应用或 Web 应用)通过 WebSocket 或长轮询建立会话。大多数操作需要客户端认证。客户端通过发送 `{login}` 数据包认证会话。认证后,客户端收到一个令牌用于后续认证。同一用户可建立多个并发会话。不支持登出设计。 会话建立后,用户可通过话题与其他用户交互。可用的话题类型: | 类型 | 名称格式 | 说明 | |------|----------|------| | `me` | `me` | 管理用户资料和接收通知;每个用户都有一个 | | `fnd` | `fnd` | 查找其他用户和话题;每个用户都有一个 | | **点对点** | `usrXXX` | 两个用户之间的通信通道 | | **群组** | `grpXXX` | 多用户通信通道;必须显式创建 | | **频道** | `chnXXX` | 只读群组;无限订阅者 | 会话通过发送 `{sub}` 数据包加入话题。`{sub}` 有三个功能: 1. 创建新话题 2. 订阅话题 3. 将会话附加到话题 会话加入话题后,用户可通过 `{pub}` 数据包生成内容。内容以 `{data}` 数据包传递给其他附加会话。 用户可通过 `{get}` 和 `{set}` 数据包查询或更新话题元数据。 话题元数据变更(如描述变更、用户加入/离开)通过 `{pres}`(在线状态)数据包报告给活跃会话。 --- ## 通用说明 ### 时间戳格式 时间戳始终表示为 [RFC 3339](http://tools.ietf.org/html/rfc3339) 格式字符串,精度到毫秒,时区始终为 UTC: ``` "2015-10-06T18:07:29.841Z" ``` ### Base64 编码 文档中提到的 base64 编码指 **base64 URL 编码**,去掉填充字符,见 [RFC 4648](http://tools.ietf.org/html/rfc4648)。 ### 消息 ID - `{data}` 数据包有服务端生成的顺序 ID:从 1 开始的十进制数字,每条消息递增 1 - 保证在每个话题内唯一 ### 客户端消息 ID 客户端可为所有数据包分配消息 ID 来连接请求和响应: - ID 是客户端定义的字符串 - 应至少在会话内唯一 - 服务端不解释,原样返回 --- ## 连接服务器 有三种网络访问方式:WebSocket、长轮询和 [gRPC](https://grpc.io/)。 ### 端点 当客户端通过 HTTP(S) 建立连接时,服务器提供以下端点: | 端点 | 用途 | |------|------| | `/v0/channels` | WebSocket 连接 | | `/v0/channels/lp` | 长轮询 | | `/v0/file/u` | 文件上传 | | `/v0/file/s` | 文件下载 | `v0` 表示 API 版本(当前为零)。 ### API Key 每个 HTTP(S) 请求必须包含 API Key,服务器按以下顺序检查: 1. HTTP 头 `X-Tinode-APIKey` 2. URL 查询参数 `apikey`(如 `/v0/file/s/abcdefg.jpeg?apikey=...`) 3. 表单值 `apikey` 4. Cookie `apikey` 演示应用包含默认 API Key。生产环境请使用 [`keygen`](../keygen) 工具生成自己的 Key。 ### 握手 连接建立后,客户端必须向服务器发送 `{hi}` 消息。服务器以 `{ctrl}` 消息响应,表示成功或错误。响应的 `params` 字段包含服务器协议版本 `"params":{"ver":"0.15"}` 等信息。 --- ### gRPC gRPC API 定义见 [proto 文件](../pbx/model.proto)。gRPC API 比本文档描述的 API 功能更多: - 允许 `root` 用户代表其他用户发送消息 - 允许删除用户 **注意**:protobuf 消息中的 `bytes` 字段期望 JSON 编码的 UTF-8 内容。例如,字符串在转换为 bytes 前应加引号: - Go: `[]byte("\"some string\"")` - Python 3: `'"another string"'.encode('utf-8')` --- ### WebSocket 消息在文本帧中发送,每帧一条消息。二进制帧保留供将来使用。 默认服务器允许任意 `Origin` 头值的连接。 --- ### 长轮询 长轮询通过 `HTTP POST`(推荐)或 `GET` 工作。 首次请求时,服务器在响应中发送包含 `sid`(会话 ID)的 `{ctrl}` 消息。长轮询客户端必须在后续每个请求中包含 `sid`(URL 或请求体)。 服务器允许所有来源的连接:`Access-Control-Allow-Origin: *` --- ### 带外大文件 大文件使用 `HTTP POST` 以 `Content-Type: multipart/form-data` 带外发送。详见[带外大文件处理](#带外大文件处理)部分。 --- ### 反向代理 Tinode 服务器可在反向代理(如 NGINX)后运行: - 通过 Unix socket 接收连接:设置 `listen` 和/或 `grpc_listen` 为 socket 文件路径,如 `unix:/run/tinode.sock` - 从 `X-Forwarded-For` HTTP 头读取对端 IP:设置 `use_x_forwarded_for` 为 `true` --- ## 用户 用户表示一个人,即消息的生产者和消费者。 ### 认证级别 用户通常分配以下两种认证级别之一: | 级别 | 说明 | |------|------| | `auth` | 已认证用户 | | `anon` | 匿名用户 | | `root` | 仅 gRPC 可访问,允许代表其他用户发送消息 | ### 用户 ID 格式 用户 ID 格式为 `usr` + base64 编码的 64 位数值,如 `usr2il9suCbuko`。 ### 用户属性 | 属性 | 类型 | 说明 | |------|------|------| | `created` | timestamp | 用户记录创建时间 | | `updated` | timestamp | `public` 或 `trusted` 最后更新时间 | | `status` | string | 账户状态(见下表) | | `username` | string | `basic` 认证使用的唯一字符串;其他用户不可见 | | `defacs` | object | 用户 P2P 会话的默认访问模式 | | `trusted` | object | 应用定义对象,由系统管理,所有人可读 | | `public` | object | 应用定义对象,描述用户,所有人可查询 | | `private` | object | 应用定义对象,仅当前用户可访问 | | `tags` | array | 发现和凭证标签 | ### 账户状态 | 状态 | 说明 | |------|------| | `ok` | 正常,默认状态 | | `susp` | 暂停,禁止访问,搜索不可见,可恢复 | | `del` | 软删除,标记为已删除但保留数据,目前不支持恢复 | | `undef` | 未定义,内部使用 | ### 多会话 用户可维护与服务器的多个并发会话。每个会话用客户端提供的 `User Agent` 字符串标记以区分客户端软件。 **不支持登出**。如需切换用户,应打开新连接并用新用户凭证认证。 --- ## 认证 认证概念上类似于 [SASL](https://en.wikipedia.org/wiki/Simple_Authentication_and_Security_Layer):通过适配器集合实现不同认证方法。 ### 内置认证方法 | 方法 | 说明 | |------|------| | `token` | 通过加密令牌认证(主要方式) | | `basic` | 通过登录名-密码对认证 | | `anonymous` | 用于临时用户,如客服聊天 | | `rest` | [元方法](../server/auth/rest/),通过 JSON RPC 使用外部认证系统 | ### 认证流程 1. **令牌是主要认证方式** - 轻量级,通常不访问数据库 - 所有处理在内存中完成 2. **其他方法用于获取令牌** - 一旦获得令牌,后续登录应使用令牌 3. **basic 认证** - `secret` 为 base64 编码的 `用户名:密码` 字符串 - 用户名不能包含冒号 `:` 4. **anonymous 认证** - 仅用于创建账户,不能用于登录 - 用户使用 `anonymous` 创建账户并获得令牌 - 如果令牌丢失或过期,用户将无法再访问账户 ### 创建账户 创建新账户时,用户必须: 1. 告知服务器后续将使用的认证方法 2. 提供共享密钥(如适用) 仅 `basic` 和 `anonymous` 可用于账户创建。 **立即登录**:设置 `{acc login=true}` 使用新账户认证当前会话。 ### 登录 通过 `{login}` 请求执行登录。仅支持 `basic` 和 `token`。 响应为 `{ctrl}` 消息: - **成功(200)**:包含可用于后续 `token` 登录的令牌 - **需要信息(300)**:请求验证凭证或多步认证挑战 - **错误(4xx)**:认证失败 令牌有服务器配置的过期时间,需要定期刷新。 ### 修改认证参数 通过 `{acc}` 请求修改认证参数(如修改登录名和密码): ```json { "acc": { "id": "1a2b3", "user": "usr2il9suCbuko", "scheme": "basic", "secret": "base64encode('new_username:new_password')" } } ``` **修改密码**:`secret` 中用户名留空,即 `base64encode(':new_password')` ### 重置密码("忘记密码") 发送 `{login}` 消息,`scheme` 设为 `reset`: ```json { "login": { "id": "1a2b3", "scheme": "reset", "secret": "base64encode('basic:email:jdoe@example.com')" } } ``` 服务器将发送包含重置说明的消息。邮件包含受限安全令牌,用户可在 `{acc}` 请求中使用该令牌设置新密码。 --- ## 凭证验证 服务器可配置要求验证与用户账户关联的某些凭证,如: - 唯一邮箱 - 电话号码 - 验证码 ### 验证流程 如果需要某些凭证: 1. 用户必须始终保持其验证状态 2. 如需更改凭证,必须先添加并验证新凭证,再删除旧凭证 ### 凭证操作 | 操作 | 消息 | |------|------| | 注册时分配 | `{acc}` | | 添加 | `{set topic="me"}` | | 删除 | `{del topic="me"}` | | 查询 | `{get topic="me"}` | | 验证 | `{login}` 或 `{acc}` | --- ## 访问控制 访问控制通过访问控制列表(ACL)管理用户对话题的访问。 ### 权限定义 权限以位图表示,通过 ASCII 字符集合表示: | 字符 | 权限 | 说明 | |------|------|------| | `N` | 无访问 | 表示权限已清除/未设置,不应使用默认权限 | | `J` | 加入 | 订阅话题的权限 | | `R` | 读取 | 接收 `{data}` 数据包的权限 | | `W` | 写入 | 向话题 `{pub}` 的权限 | | `P` | 在线状态 | 接收在线状态更新 `{pres}` 的权限 | | `A` | 审批 | 批准加入请求、删除和禁止成员的权限(管理员) | | `S` | 分享 | 邀请他人加入话题的权限 | | `D` | 删除 | 硬删除消息的权限;仅所有者可完全删除话题 | | `O` | 所有者 | 话题所有者;可分配任何权限给成员、更改话题描述、删除话题;单所有者;某些话题无所有者 | ### 实际访问权限 实际访问权限 = `want` AND `given`(按位与) ### 默认访问权限 默认访问为两类用户定义: - **已认证用户**:`auth` - **匿名用户**:`anon` 默认访问值作为 "given" 权限应用于所有新订阅。 --- ## 话题 话题是一个或多个人的命名通信通道。话题有持久属性,可通过 `{get what="desc"}` 查询。 ### 话题属性 **与用户无关的属性**: | 属性 | 说明 | |------|------| | `created` | 话题创建时间 | | `updated` | `trusted`、`public`、`private` 最后更新时间 | | `touched` | 最后一条消息发送时间 | | `defacs` | 话题默认访问模式 | | `seq` | 最新 `{data}` 消息的服务端顺序 ID | | `trusted` | 应用定义对象,系统管理,所有人可读 | | `public` | 应用定义对象,描述话题,所有订阅者可读 | **用户相关属性**: | 属性 | 说明 | |------|------| | `acs` | 当前用户的访问权限 | | `private` | 应用定义对象,仅当前用户可访问 | --- ### me 话题 `me` 话题在账户创建时自动为每个用户创建。用途: - 管理账户信息 - 接收关注的人和话题的在线状态通知 **特点**: - 无所有者 - 不能删除或取消订阅 - 可以 `leave`(停止通信,表示离线) - 只读(`{pub}` 消息被拒绝) **`{get what="desc"}`**:返回用户参数,`public` 更新会影响所有 P2P 话题。 **`{get what="sub"}`**:返回用户订阅的话题列表,而非用户对 `me` 的订阅。 返回字段: - `seq`:话题最后消息 ID - `recv`:用户报告已收到的 seq 值 - `read`:用户报告已读的 seq 值 - `seen`:P2P 订阅中,对方最后在线时间和 User Agent --- ### slf 话题 `slf`(self)话题用于存储信息,如书签或保存的消息。发送到 `slf` 的消息仅发送者可访问。 用户首次订阅时自动创建。 --- ### fnd 话题与标签 `fnd` 话题在账户创建时自动为每个用户创建,用于发现其他用户和群组话题。 #### 标签 标签是用于发现的任意大小写不敏感的 Unicode 字符串: - 最大 96 字符 - 可包含字母、数字和 `_`, `.`, `+`, `-`, `@`, `#`, `!`, `?` - 可有前缀(命名空间):如 `tel:+14155551212`、`email:alice@example.com` - 某些带前缀标签可强制唯一 - 某些标签可强制不可变 #### 查询语言 查询字符串包含原子术语,用空格或逗号分隔: - **空格**:`AND` 操作 - **逗号**:`OR` 操作 - `OR` 优先于 `AND` **示例**: - `flowers`:匹配包含 `flowers` 标签 - `flowers travel`:同时包含 `flowers` 和 `travel` - `flowers, travel`:包含 `flowers` 或 `travel` - `flowers travel, puppies`:包含 `flowers` 且包含 `travel` 或 `puppies` #### 查询方式 设置 `fnd` 话题的 `public` 或 `private` 为查询字符串,然后发送 `{get topic="fnd" what="sub"}`。 - `private`:跨会话和设备持久化,适合大查询(如通讯录匹配) - `public`:不保存到数据库,适合简短特定查询 --- ### 点对点话题 点对点(P2P)话题表示严格两个用户之间的通信通道。 **特点**: - 话题名对两个参与者不同:每人看到的是对方的用户 ID - 例:用户 `usrOj0B3-gSBSs` 和 `usrIU_LOVwRNsc` 的 P2P 话题: - 第一个用户看到:`usrIU_LOVwRNsc` - 第二个用户看到:`usrOj0B3-gSBSs` - 无所有者 - 内部存储为 `p2p` + 两个 64 位用户 ID 的拼接(数值小的在前) **创建**:发送 `{sub topic="usrXXX"}`(对方用户 ID)。 **`public`**:用户相关,自动同步到所有 P2P 话题。 --- ### 群组话题 群组话题表示多个用户之间的通信通道。 **名称格式**:`grp` 或 `chn` + base64 URL 字符串 **特点**: - 支持有限数量订阅者(`max_subscriber_count` 配置) - 每个订阅者的访问权限单独管理 - 可启用频道功能,支持无限只读订阅者(读者) **创建**: - 普通群组:`{sub topic="new"}` - 频道:`{sub topic="nch"}` - 服务器返回新话题名,如 `grpYiqEXb4QY6s` - 创建者成为所有者 **频道与普通群组的区别**: | 特性 | 普通群组 | 频道 | |------|----------|------| | 创建 | `{sub topic="new"}` | `{sub topic="nch"}` | | 订阅 | `{sub topic="grpXXX"}` | `{sub topic="chnXXX"}` | | 消息 From 字段 | 包含发送者 | 无 | | 默认权限 | JRWPS | 无 | | 加入通知 | 有 | 无 | --- ### sys 话题 `sys` 话题是与系统管理员通信的始终可用通道。 **普通用户**: - 不能订阅 `sys` - 可以不订阅就向 `sys` 发布(如举报滥用) **root 用户**: - 可以订阅 `sys` - 订阅后接收其他用户发送到 `sys` 的消息 --- ## 消息 消息是逻辑关联的数据集合。消息以 JSON 格式的 UTF-8 文本传递。 ### 消息格式说明 - 所有客户端到服务端消息可有可选 `id` 字段 - 服务器要求严格有效的 JSON(包括字段名的双引号) - 清除字段:发送单个 Unicode DEL 字符 `\u2421`(而非 `null`) - 未知字段被静默忽略 --- ## 客户端到服务端消息 每条消息可包含顶层 `extra` 字段: ```json { "abc": { ... }, // 主载荷 "extra": { "attachments": ["/v0/file/s/sJOD_tZDPz0.jpg"], "obo": "usr2il9suCbuko", "authlevel": "auth" } } ``` | 字段 | 说明 | |------|------| | `attachments` | 带外附件 URL 数组,递增文件使用计数 | | `obo` | root 用户设置的代理用户 ID | | `authlevel` | 代理时的认证级别 | --- ### `{hi}` - 握手 客户端向服务器告知其版本和用户代理。必须是第一条消息。 ```json { "hi": { "id": "1a2b3", "ver": "0.15.8-rc2", "ua": "JS/1.0 (Windows 10)", "dev": "L1iC2...dNtk2", "platf": "android", "lang": "en-US", "tenant": "acme" } } ``` | 字段 | 必需 | 说明 | |------|------|------| | `id` | 否 | 客户端消息 ID | | `ver` | 是 | 支持的协议版本 | | `tenant` | 是 | 企业码;首次握手时解析并固定当前 Session 的租户 | | `ua` | 否 | 用户代理字符串 | | `dev` | 否 | 设备唯一标识(用于推送) | | `platf` | 否 | 平台:android/ios/web | | `lang` | 否 | 客户端语言 | 服务器响应 `{ctrl}`,包含 `build`、`ver`、`sid` 以及公开租户信息等参数。租户绑定后不能在当前 Session 中切换。 --- ### `{acc}` - 账户 创建用户或更新认证凭证。 ```json { "acc": { "id": "1a2b3", "user": "newABC123", "token": "XMgS...8+BO0=", "scheme": "basic", "secret": "base64encode('username:password')", "login": true, "tags": ["alice johnson"], "cred": [{"meth": "email", "val": "alice@example.com"}], "desc": { "defacs": {"auth": "JRWS", "anon": "N"}, "public": {...}, "private": {...} } } } ``` | 字段 | 说明 | |------|------| | `user` | `"new"` 创建新用户;默认当前用户 | | `token` | 未认证时使用的令牌 | | `scheme` | 认证方案:basic/anonymous | | `secret` | base64 编码的密钥 | | `login` | 是否立即使用新账户登录 | | `tags` | 发现标签数组 | | `cred` | 凭证数组 | | `desc` | 用户初始化数据 | --- ### `{login}` - 登录 认证当前会话。 ```json { "login": { "id": "1a2b3", "scheme": "basic", "secret": "base64encode('username:password')", "cred": [{"meth": "email", "resp": "178307"}] } } ``` | 字段 | 说明 | |------|------| | `scheme` | 认证方案:basic/token/reset | | `secret` | base64 编码的密钥 | | `cred` | 凭证验证响应 | 服务器响应 `{ctrl}`,`params` 包含: - `user`:登录用户 ID - `token`:加密令牌 - `expires`:过期时间 --- ### `{sub}` - 订阅 功能: 1. 创建新话题 2. 订阅现有话题 3. 将会话附加到话题 4. 获取话题数据 ```json { "sub": { "id": "1a2b3", "topic": "me", "set": { "desc": {"defacs": {"auth": "JRWS", "anon": "N"}}, "sub": {"mode": "JRWS"}, "tags": ["email:alice@example.com"] }, "get": { "what": "desc sub data", "desc": {"ims": "2015-10-06T18:07:30.038Z"}, "sub": {"limit": 20}, "data": {"since": 123, "before": 321, "limit": 20} } } } ``` | 字段 | 说明 | |------|------| | `topic` | 话题名;`new` 创建群组,`nch` 创建频道 | | `set` | 设置参数(镜像 `{set}`) | | `get` | 查询参数(镜像 `{get}`) | --- ### `{leave}` - 离开 功能: 1. 离开话题但不取消订阅(`unsub=false`) 2. 取消订阅(`unsub=true`) ```json { "leave": { "id": "1a2b3", "topic": "grp1XUtEhjv6HND", "unsub": true } } ``` --- ### `{pub}` - 发布 向话题订阅者分发内容。 ```json { "pub": { "id": "1a2b3", "topic": "grp1XUtEhjv6HND", "noecho": false, "head": {"mime": "text/x-drafty"}, "content": {...} } } ``` | 字段 | 说明 | |------|------| | `noecho` | 禁止回显(不接收自己发布的消息) | | `head` | 消息头键值对 | | `content` | 应用定义内容 | **`head` 字段值**: | 字段 | 说明 | |------|------| | `attachments` | 附件路径数组 | | `auto` | 消息由机器人自动发送 | | `forwarded` | 转发消息的原始消息 ID | | `mentions` | 提及的用户 ID 数组 | | `mime` | 内容 MIME 类型 | | `replace` | 替换的消息 ID | | `reply` | 回复的消息 ID | | `thread` | 线程首消息 ID | --- ### `{get}` - 查询 查询话题元数据或消息历史。 ```json { "get": { "id": "1a2b3", "topic": "grp1XUtEhjv6HND", "what": "sub desc data del cred", "desc": {"ims": "2015-10-06T18:07:30.038Z"}, "sub": {"user": "usr2il9suCbuko", "limit": 20}, "data": {"since": 123, "before": 321, "limit": 20}, "del": {"since": 5, "before": 12, "limit": 25} } } ``` **`what` 值**: | 值 | 说明 | |----|------| | `desc` | 话题描述 | | `sub` | 订阅者列表 | | `tags` | 索引标签 | | `data` | 消息历史 | | `del` | 删除历史 | | `cred` | 凭证 | | `aux` | 辅助数据 | --- ### `{set}` - 更新 更新话题元数据。 ```json { "set": { "id": "1a2b3", "topic": "grp1XUtEhjv6HND", "desc": { "defacs": {"auth": "JRWP", "anon": "JRW"}, "public": {...} }, "sub": { "user": "usr2il9suCbuko", "mode": "JRWP" }, "tags": ["email:alice@example.com"] } } ``` --- ### `{del}` - 删除 删除消息、订阅、话题、用户。 ```json { "del": { "id": "1a2b3", "topic": "grp1XUtEhjv6HND", "what": "msg", "hard": false, "delseq": [{"low": 123, "hi": 125}, {"low": 156}], "user": "usr2il9suCbuko" } } ``` **`what` 值**: | 值 | 说明 | |----|------| | `msg` | 删除消息 | | `sub` | 删除订阅 | | `topic` | 删除话题 | | `user` | 删除用户 | | `cred` | 删除凭证 | **消息删除**: - `hard=false`:软删除(仅对当前用户隐藏),需 `R` 权限 - `hard=true`:硬删除(物理删除),需 `D` 权限 --- ### `{note}` - 通知 客户端生成的临时通知,如输入通知或送达回执。 ```json { "note": { "topic": "grp1XUtEhjv6HND", "what": "kp", "seq": 123, "unread": 10 } } ``` **`what` 值**: | 值 | 说明 | |----|------| | `kp` | 按键(正在输入) | | `kpa` | 正在录制语音消息 | | `kpv` | 正在录制视频消息 | | `recv` | 消息已接收 | | `read` | 消息已读取 | | `call` | 视频通话状态更新 | | `data` | 结构化数据包 | --- ## 服务端到客户端消息 对特定请求的响应消息包含等于原始消息 ID 的 `id` 字段。大多数消息有 `ts` 时间戳字段。 --- ### `{data}` - 数据内容 话题中发布的内容。唯一持久化到数据库的消息。 ```json { "data": { "topic": "grp1XUtEhjv6HND", "from": "usr2il9suCbuko", "head": {...}, "ts": "2015-10-06T18:07:30.038Z", "seq": 123, "content": {...} } } ``` | 字段 | 说明 | |------|------| | `topic` | 发布此消息的话题 | | `from` | 发布者用户 ID | | `head` | 消息头(从 `{pub}` 原样传递) | | `seq` | 服务端顺序 ID(话题内唯一) | | `content` | 应用定义内容 | --- ### `{ctrl}` - 控制响应 表示错误或成功条件的通用响应。 ```json { "ctrl": { "id": "1a2b3", "topic": "grp1XUtEhjv6HND", "code": 200, "text": "OK", "params": {...}, "ts": "2015-10-06T18:07:30.038Z" } } ``` | 字段 | 说明 | |------|------| | `code` | 状态码(遵循 HTTP 状态码模型) | | `text` | 结果详情文本 | | `params` | 上下文相关参数 | --- ### `{meta}` - 元数据 话题元数据或订阅者信息,响应 `{get}`、`{set}` 或 `{sub}`。 ```json { "meta": { "id": "1a2b3", "topic": "grp1XUtEhjv6HND", "ts": "2015-10-06T18:07:30.038Z", "desc": { "created": "2015-10-24T10:26:09.716Z", "defacs": {"auth": "JRWP", "anon": "N"}, "acs": {"want": "JRWP", "given": "JRWP", "mode": "JRWP"}, "seq": 123, "read": 112, "recv": 115, "public": {...} }, "sub": [ { "user": "usr2il9suCbuko", "updated": "2015-10-24T10:26:09.716Z", "acs": {"want": "JRWP", "given": "JRWP", "mode": "JRWP"}, "read": 112, "online": true } ], "tags": ["email:alice@example.com"], "del": {"clear": 3, "delseq": [{"low": 15}]} } } ``` --- ### `{pres}` - 在线状态 通知客户端重要事件。 ```json { "pres": { "topic": "me", "src": "grp1XUtEhjv6HND", "what": "on", "seq": 123, "ua": "Tinode/1.0 (Android 2.2)" } } ``` **`what` 值**: | 值 | 说明 | |----|------| | `on` | 上线 | | `off` | 下线 | | `ua` | 用户代理变更 | | `upd` | 话题描述变更 | | `tags` | 标签变更 | | `acs` | 访问权限变更 | | `gone` | 话题不可用 | | `term` | 订阅终止 | | `msg` | 新消息可用 | | `read` | 消息已读 | | `recv` | 消息已接收 | | `del` | 消息已删除 | --- ### `{info}` - 信息 转发的客户端通知 `{note}`。 ```json { "info": { "topic": "grp1XUtEhjv6HND", "from": "usr2il9suCbuko", "what": "read", "seq": 123 } } ``` --- ## 内容格式 `{pub}` 和 `{data}` 的 `content` 字段格式由应用定义,服务器不强制特定结构。 ### 支持的内容类型 | 类型 | 说明 | |------|------| | 纯文本 | 普通字符串 | | Drafty | 富文本格式,见 [Drafty 文档](./drafty.md) | **使用 Drafty**:消息头必须设置 `"head": {"mime": "text/x-drafty"}`。 --- ## 可信、公开、私有、辅助字段 话题有 `trusted`、`public`、`aux` 字段,订阅有 `private` 字段。 ### 访问控制区别 | 字段 | 写权限 | 读权限 | |------|--------|--------| | `trusted` | ROOT 用户 | 所有人 | | `public` | 所有者或用户 | 所有人 | | `aux` | 话题管理员 | 订阅者 | | `private` | 用户自己 | 用户自己 | ### trusted 格式 ```json { "trusted": { "verified": true, "staff": true, "danger": false } } ``` | 字段 | 说明 | |------|------| | `verified` | 已验证/可信用户或话题标识 | | `staff` | 属于服务器管理团队 | | `danger` | 不可信标识 | ### public 格式 群组、P2P、系统话题的 `public` 字段应为 [theCard](./thecard.md) 格式。 `fnd` 话题的 `public` 应为搜索查询字符串。 ### private 格式 ```json { "private": { "comment": "some comment", "arch": true, "accepted": "JRWS", "tpins": ["grpmiKBkQVXnm3P"] } } ``` | 字段 | 说明 | |------|------| | `comment` | 用户对话题或对方的备注 | | `arch` | 话题已归档 | | `accepted` | 用户接受的 'given' 模式 | | `tpins` | 置顶的话题 ID(仅 `me` 话题) | ### aux 格式 ```json { "aux": { "pins": [1001, 23456] } } ``` | 字段 | 说明 | |------|------| | `pins` | 置顶的消息 ID 数组 | --- ## 带外大文件处理 大文件通过 `HTTP POST` 以 `multipart/form-data` 带外处理。 ### 端点 | 端点 | 用途 | |------|------| | `/v0/file/u` | 文件上传 | | `/v0/file/s` | 文件下载 | ### 认证 需要 API Key 和登录凭证,检查顺序: 1. HTTP 头 `Authorization` 2. URL 参数 `auth` 和 `secret` 3. 表单值 `auth` 和 `secret` 4. Cookie `auth` 和 `secret` ### 上传流程 1. 创建 RFC 2388 multipart 请求 2. POST 到服务器 3. 响应 `307 Temporary Redirect` 或 `200 OK` + `{ctrl}` 4. `ctrl.params.url` 包含文件路径 ```json { "ctrl": { "params": { "url": "/v0/file/s/mfHLxDWFhfU.pdf" }, "code": 200, "text": "ok" } } ``` ### 在消息中使用 将 URL 用于 `{pub}` 消息的 Drafty 内容: ```json { "pub": { "head": {"mime": "text/x-drafty"}, "content": { "ent": [{"data": {"mime": "image/jpeg", "ref": "/v0/file/s/sJOD_tZDPz0.jpg"}}] } }, "extra": { "attachments": ["/v0/file/s/sJOD_tZDPz0.jpg"] } } ``` **重要**:必须在 `extra.attachments` 中列出使用的 URL,服务器使用此字段维护文件使用计数。 --- ## 推送通知 Tinode 使用编译时适配器处理推送通知。 ### 内置适配器 | 适配器 | 支持平台 | |--------|----------| | Tinode Push Gateway (TNPG) | Android (Play Services)、iOS、Web 浏览器(Safari 除外) | | Google FCM | Android (Play Services)、iOS、Web 浏览器(Safari 除外) | | stdout | 调试、测试、日志 | ### 通知载荷 ```json { "topic": "grpnG99YhENiQU", "xfrom": "usr2il9suCbuko", "ts": "2019-01-06T18:07:30.038Z", "seq": "1234", "mime": "text/x-drafty", "content": "Lorem ipsum..." } ``` --- ## 视频通话 详见 [视频通话文档](call-establishment.md)。 --- ## 链接预览 Tinode 提供可选服务帮助客户端生成链接预览。 **端点**:`/v0/urlpreview?url=...` **响应**: ```json { "title": "Page title", "description": "Page description", "image_url": "https://..." } ``` 需要认证,方式同[带外大文件](#带外大文件处理)。 --- ## 附录:状态码 服务器响应使用 HTTP 状态码模型: | 状态码范围 | 说明 | |------------|------| | 2xx | 成功 | | 3xx | 需要更多信息 | | 4xx | 客户端错误 | | 5xx | 服务器错误 | --- ## 附录:错误消息 错误以 JSON 返回:`{"err": "error-message"}` | 错误 | 说明 | |------|------| | `internal` | 数据库失败或其他内部错误 | | `malformed` | 请求无法解析 | | `failed` | 认证失败 | | `duplicate value` | 重复凭证 | | `unsupported` | 不支持的操作 | | `expired` | 密钥已过期 | | `policy` | 策略违规 | | `credentials` | 需要验证凭证 | | `not found` | 对象未找到 | | `denied` | 操作不允许 |