@cg-devcenter/rtc-fpnn-webjs-sdk 1.0.0-cg.2 → 1.0.0-cg.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/USAGE.md CHANGED
@@ -1,168 +1,546 @@
1
- # RTC WebSocket SDK API
1
+ # RTC WebSocket SDK TLV v1 接入与 API 参考
2
2
 
3
- 本页定义公开 JavaScript API。TLV v1 的 URI、wire 字段和返回结构见
4
- [TLV v1 Client 契约](TLV_CONTRACT.md)。
3
+ ## 1. 文档范围
5
4
 
6
- ## 通用约定
5
+ 本文面向 TLV v1 接入者,按“跑通连接 → 了解生命周期 → 查 API 参数”组织。参数与返回值详解覆盖下方支持矩阵标记为 TLV v1 支持的 API;矩阵同时列出未开放 API,供确认边界。它不是 FPNN 传统 API 的完整参考。
7
6
 
8
- - 新接入使用 `protocol: 'tlv-v1'`。
9
- - 除 `login` 外,回调为 `callback(err, data)`;`timeout` 单位为毫秒。
10
- - `uid`、`gid`、`rid`、`mid`、游标和时间戳超出安全整数范围时使用 `RTMConfig.Int64`。
11
- - `RTMConfig.TLV_SERVER_PUSH` 为 TLV Push 名称集合。
7
+ | 入口 | 运行环境 | 能力范围 |
8
+ | --- | --- | --- |
9
+ | `RTMClient` + `protocol: 'tlv-v1'` | 浏览器;Node.js 需注入 `webSocketFactory` | 本文 TLV v1 支持矩阵列出的 API。新接入推荐此入口。 |
10
+ | `RTMClient` + `protocol: 'fpnn'` | 浏览器 | 旧版兼容入口;兼容 API 仍受 FPNN 服务端能力和部署配置限制。 |
11
+ | `RTMSharedWorkerClient` | 支持 SharedWorker 的浏览器、同源页面 | TLV v1 / FPNN;多个同源页面共享一个 UID 的 RTC 会话,并由 SDK 路由实时 Push。 |
12
+
13
+ 本文给出 TLV v1 方法参数和常见返回约定,不重复 TLV wire 字段的完整 schema。TLV Gate Answer 字段、数据类型和状态码以[TLV v1 Client 契约](TLV_CONTRACT.md)为准;TypeScript 完整声明见 [`dist/rtm.d.ts`](../dist/rtm.d.ts)。`fpnn` 的具体兼容范围以旧服务端为准;旧接口参数说明见[用户](Users.md)、[群组](Groups.md)、[房间](Rooms.md)、[消息](Messages.md)、[聊天](Chat.md)、[数据](Data.md)、[系统](System.md)、[文件](Files.md)、[好友](Friends.md)、[增值服务](ValueAdded.md)和[事件处理](EventProcess.md)文档。
14
+
15
+ 需要从 FPNN 迁移到 TLV v1 时,先看 [协议迁移对照](PROTOCOL_MIGRATION.md):它列出了相同 `RTMClient` 业务 API 对应的旧 FPNN method 与 TLV URI,并标明支持边界和兼容差异。
16
+
17
+ ### 交给测试人员或 AI 接入前
18
+
19
+ 请随文提供以下环境信息,避免接入者猜测项目配置或尝试从浏览器获取项目密钥:
20
+
21
+ - SDK 版本:本指南中的 Promise API、SharedWorker API 和 Vite 入口从 `1.0.0-cg.3` 起提供;此前发布的 `1.0.0-cg.2` 不包含这些能力。若 `.3` 尚未发布,需同时提供 `.tgz` 包或源码工作区。
22
+ - RTC 环境:`pid`、协议(通常为 `tlv-v1`)、完整 WebSocket endpoint,以及应用页面的 origin。
23
+ - 测试身份:发送 UID、接收 UID、账号权限和预期验证场景;不要在文档或任务描述中粘贴长期凭证。
24
+ - credential 获取方式:由哪个业务服务端接口签发、请求需要的登录态/参数、响应 JSON 字段名和 credential 有效期。项目 `SecretKey` 只保留在受控服务端或本机签发工具中。
25
+ - 页面模式:普通单页连接,或同 UID 多页面共享连接;共享模式还要说明 Worker 文件的部署方式和需要验证的浏览器。
26
+
27
+ 缺少以上信息时,接入者仍可完成 API 集成,但不能独立验证真实 Gate 登录和消息收发。
28
+
29
+ ## 2. 快速开始:登录、收 Push、发消息、关闭
30
+
31
+ ### 默认共享接入(当前源码)
32
+
33
+ 新入口 `createClient(options)` 默认启用 SharedWorker,默认使用 TLV;显式 `protocol: 'fpnn'` 也可共享。**已发布的 `1.0.0-cg.3` 尚不包含此入口和 FPNN 共享扩展**,请使用本地构建/yalc 或后续版本。旧 `RTMClient`、`RTMSharedWorkerClient` 的构造参数和行为保留。
34
+
35
+ Vite 项目从 `/vite` 导入,普通 HTML 把 SDK 与 Worker bundle 部署到同源目录;业务不需要填写 Worker 名称。下面可作为 Vite 页面脚本使用,替换项目配置和 credential 接口:
36
+
37
+ ```js
38
+ import rtm from '@cg-devcenter/rtc-fpnn-webjs-sdk/vite';
39
+
40
+ const uid = '10001';
41
+ const peerUid = '10002';
42
+ const client = rtm.createClient({
43
+ pid: 12345,
44
+ tlvEndpoint: 'wss://<gate-host>/service/websocket',
45
+ autoReconnect: true,
46
+ });
47
+
48
+ client.processor.on(rtm.RTMConfig.TLV_SERVER_PUSH.recvMessage, (event) => {
49
+ console.log('Received', event);
50
+ });
51
+ client.on('SharedSessionState', (session) => console.log('Session', session));
52
+ client.on('SharedWorkerError', (error) => console.error('Shared connection', error));
53
+
54
+ async function start() {
55
+ // 按 UID 挂接共享会话,尚未登录时不会建立 Gate WebSocket。
56
+ const session = await client.whenReady(uid);
57
+ let credential;
58
+ if (session.status === 'idle') {
59
+ const response = await fetch('/api/rtc/credential?uid=' + encodeURIComponent(uid), {
60
+ credentials: 'same-origin',
61
+ });
62
+ if (!response.ok) throw new Error('Credential request failed: ' + response.status);
63
+ credential = (await response.json()).credential;
64
+ if (!credential) throw new Error('Credential is missing');
65
+ }
66
+ await client.promises.login(uid, credential);
67
+ await client.promises.sendChat(peerUid, 'hello', '{}');
68
+ }
69
+
70
+ start().catch(console.error);
71
+ // 页面离开由 SDK 自动 detach;组件卸载时也可 client.destroy()。
72
+ ```
73
+
74
+ 已持有 credential 时直接 `client.promises.login(uid, credential)` 即可,SDK 从登录参数取 UID,无需先调用 `whenReady()`。第一次登录需要服务端 credential;后续同 UID 页面可省略。`on()` / `processor.on()` 可在登录之前订阅,不需要业务缓存监听器。
75
+
76
+ UID 使用正整数或十进制字符串;超过 JavaScript 安全整数范围时必须用字符串,最大为 uint64。SDK 规范化前导零并转为协议整数,不会把 UID 字符串直接发给 FPNN。无效或超范围的 UID 返回 `RTM_SHARED_UID_INVALID`,缺少 UID 返回 `RTM_SHARED_UID_REQUIRED`。
77
+
78
+ | 配置 / API | 默认与边界 |
79
+ | --- | --- |
80
+ | `createClient(options)` | 使用扁平客户端配置,默认 `sharing: true`、`protocol: 'tlv-v1'`;浏览器没有 SharedWorker 时明确抛出 `RTM_SHARED_WORKER_UNSUPPORTED`。 |
81
+ | `sharing: false` | 返回独立的 `RTMClient`,新入口仍默认 TLV;Node.js 或需要文件上传/函数型配置时显式选择,FPNN 仍仅支持浏览器。不会因共享初始化失败自动降级。 |
82
+ | `uid` | 可选;提供时立即挂接共享 Worker,适合收件箱自动观察其他页面登录。省略时在 `login()` / `loginIfNeeded()` / `whenReady(uid)` 时挂接。一个实例固定一个 UID;切换账号需 detach 后创建新实例。 |
83
+ | `workerName` / `workerUrl` | 可选高级覆盖项;默认名字由 PID、协议、UID 生成,不包含 endpoint。默认路径沿用脚本同目录规则,Vite `/vite` 自动打包。`workerNamespace` 仍可隔离应用。 |
84
+ | `whenReady(uid?)` | 返回本页挂接后的共享会话快照。尚未绑定 UID 且未传 UID 时立即 reject `RTM_SHARED_UID_REQUIRED`,不会无限等待。 |
85
+ | `promises.login(uid, credential?, timeout?)` | 成功 resolve `{accepted: true, errorCode}`,RTC 拒绝或本地/Worker 错误 reject;并发同 UID 登录只发一次鉴权。`loginIfNeeded()` 保留 `{accepted, errorCode, reused, uid}` 的结果约定。 |
86
+ | `login(uid, credential?, callback?, timeout?)` | RTC 拒绝与本地/Worker 失败均回调 `callback(false, numericCode)`;本地/Worker 失败的详细错误通过 `SharedWorkerError` 事件提供。仅 `accepted === true` 表示登录成功;`promises.login()` 会 reject 详细错误。 |
87
+ | `destroy()` | 只 detach 当前页面;最后一页离开后按 `idleShutdownTimeout` 回收共享连接。`bye()` / `close()` 才结束所有共享页面的会话。 |
88
+ | 会话路由与业务 API | 沿用 `setConversation()`、`processor`、重连和原协议的业务返回;共享模式不支持文件发送或函数型配置。绑定 UID 前的业务请求明确报 `RTM_SHARED_UID_REQUIRED`。 |
89
+
90
+ 共享还要求同一浏览器配置文件、origin 和 Worker URL。**同 PID、协议、UID 的页面必须使用相同服务端地址和 `clientOptions`**;地址、鉴权配置、重连策略或其他参数不同会初始化失败,不会覆盖已有连接。不同 PID、协议或 UID 自动分开。新入口的默认名称与旧 `RTMSharedWorkerClient` 的 endpoint 参与命名规则不同:同 UID 的各页应统一使用新入口,或显式传相同 Worker 名称和配置,不要在部分页面切换后假定已经共享。
91
+
92
+ ### 原有单连接接入
93
+
94
+ 先通过业务服务端为当前 UID 签发 credential。浏览器不能持有项目 SecretKey。下面的 `/api/rtc/credential` 是业务服务端接口占位,请替换成你们自己的鉴权接口;它应返回 `{ "credential": "..." }`。
95
+
96
+ 将 SDK 浏览器 bundle 放入页面后,这段 HTML 可直接作为最小接入模板。替换 `pid`、`tlvEndpoint` 和用户 ID,并让 credential 接口能签发发送 UID 的 credential,即可连到真实 Gate:
97
+
98
+ ```html
99
+ <script src="/assets/rtm.min.js"></script>
100
+ <script>
101
+ const rtm = window.rtm;
102
+ const config = {
103
+ pid: 12345,
104
+ tlvEndpoint: 'wss://<gate-host>/service/websocket',
105
+ uid: '10001',
106
+ peerUid: '10002',
107
+ };
108
+ const timeoutMs = 12000;
109
+
110
+ async function getCredential(uid) {
111
+ const response = await fetch(
112
+ '/api/rtc/credential?uid=' + encodeURIComponent(uid),
113
+ { credentials: 'same-origin' },
114
+ );
115
+ if (!response.ok) throw new Error('Credential request failed: ' + response.status);
116
+ const result = await response.json();
117
+ if (!result.credential) throw new Error('Credential is missing');
118
+ return result.credential;
119
+ }
120
+
121
+ async function startChat() {
122
+ const credential = await getCredential(config.uid);
123
+ const client = new rtm.RTMClient({
124
+ protocol: 'tlv-v1',
125
+ pid: config.pid,
126
+ tlvEndpoint: config.tlvEndpoint,
127
+ autoReconnect: true,
128
+ requestTimeout: timeoutMs,
129
+ });
130
+ let loggedIn = false;
131
+
132
+ client.on('ErrorRecorder', (error) => console.error('RTC connection error', error));
133
+ client.processor.on(rtm.RTMConfig.TLV_SERVER_PUSH.recvMessage, (event) => {
134
+ console.log('Received direct message', event);
135
+ });
12
136
 
13
- ## 构造参数
137
+ try {
138
+ const uid = new rtm.RTMConfig.Int64(config.uid);
139
+ const peerUid = new rtm.RTMConfig.Int64(config.peerUid);
140
+ await client.promises.login(uid, credential, timeoutMs);
141
+ loggedIn = true;
14
142
 
15
- `new rtm.RTMClient(options)`
143
+ const sent = await client.promises.sendChat(
144
+ peerUid, 'Hello from RTM SDK', '{}', undefined, timeoutMs,
145
+ );
146
+ console.log('Message accepted by Gate', sent);
147
+ } finally {
148
+ if (loggedIn) {
149
+ try {
150
+ await client.promises.bye(timeoutMs);
151
+ } catch (error) {
152
+ // TLV bye timeout/error closes the socket locally; keep cleanup bounded.
153
+ console.warn('Graceful close failed', error);
154
+ }
155
+ }
156
+ client.destroy();
157
+ }
158
+ }
16
159
 
17
- | 参数 | 类型 | 说明 |
160
+ startChat().catch((error) => console.error('RTM startup failed', error));
161
+ </script>
162
+ ```
163
+
164
+ `sendChat` 的 `attrs` 是额外信息字符串;没有额外字段时传 `'{}'`。消息 ID `mid` 可省略,SDK 会生成。`promises.login()` 在鉴权拒绝时会 reject,业务请求的 Promise 则在 Gate 返回错误或请求超时时 reject。
165
+
166
+ ### 引用与运行环境
167
+
168
+ ```js
169
+ // CommonJS
170
+ const rtm = require('@cg-devcenter/rtc-fpnn-webjs-sdk');
171
+ ```
172
+
173
+ Vite 项目可从专用 ESM 入口导入;该入口会把 SharedWorker 脚本作为构建资源并自动传给 `RTMSharedWorkerClient`,不需要手动写 Worker 文件路径:
174
+
175
+ ```js
176
+ import rtm from '@cg-devcenter/rtc-fpnn-webjs-sdk/vite';
177
+ ```
178
+
179
+ 浏览器可加载 `dist/rtm.min.js` 并从 `window.rtm` 取 SDK。Node.js 仅支持 TLV v1;需要在 `RTMClient` 选项中传入 `webSocketFactory(url)`,返回对象须支持 `send(data)`、`close(code?, reason?)` 及 `onopen`、`onmessage`、`onerror`、`onclose`。SDK 不附带 Node WebSocket 实现。`fpnn` 和 SharedWorker 仅面向浏览器。
180
+
181
+ ## 3. 生命周期与错误处理
182
+
183
+ ### 初始化和生命周期 API
184
+
185
+ | API | 参数和结果 |
186
+ | --- | --- |
187
+ | `new RTMClient(options)` | 创建客户端;`pid` 必填,必须显式设 `protocol: 'tlv-v1'` 才使用 TLV。构造参数见下表。 |
188
+ | `login(uid, credential, callback, timeout?)` | `callback(accepted, errorCode)`。仅 `accepted === true` 表示进入登录态。`timeout` 限制 auth 请求;WebSocket 握手由 `connectionTimeout` 限制。 |
189
+ | `promises.login(uid, credential, timeout?)` | 登录成功 resolve `{accepted: true, errorCode}`;登录拒绝或失败 reject,错误对象带 `code`。 |
190
+ | `bye(timeout?, callback?)` | 发协议级关闭请求;Gate 应答后 callback 为 `(err, data)`。TLV 失败或超时会关闭底层连接。Promise 版本等待应答并在失败时 reject。 |
191
+ | `close()` | 立即关闭当前连接,不等待协议级关闭应答。 |
192
+ | `startAutoReconnect()` | 关闭当前连接并允许 SDK 按配置重连。 |
193
+ | `destroy()` | 立即关闭连接并释放客户端资源;正在等待的请求会以错误结束。 |
194
+ | `getServerTime(timeout, callback)` | 查询服务端时间;TLV 返回 `server_ms`。 |
195
+ | `addAttrs(attrs, timeout, callback)` / `getAttrs(timeout, callback)` | 设置或读取连接属性。 |
196
+ | `addDebugLog(msg, attrs, timeout, callback)` | 向 Gate 发送调试日志。 |
197
+
198
+ ### RTMClient 构造参数
199
+
200
+ 省略可选项时使用默认值;除非另有说明,时间单位为毫秒。
201
+
202
+ | 参数 | 类型 | 必填 / 默认值 | 说明 |
203
+ | --- | --- | --- | --- |
204
+ | `pid` | `number` | 必填 | RTC 项目 ID。 |
205
+ | `protocol` | `'tlv-v1' \| 'fpnn'` | 默认 `'fpnn'` | 新接入请显式设为 `'tlv-v1'`。 |
206
+ | `tlvEndpoint` | `string` | TLV 必填 | Gate 提供的完整 `ws://` 或 `wss://` URL,含部署要求的路径,通常以 `/service/websocket` 结尾。 |
207
+ | `autoReconnect` | `boolean` | `true` | 自动重连开关。终止性鉴权错误不会无限重试。 |
208
+ | `connectionTimeout` | `number` | `30000` | 建立 WebSocket 连接的超时。 |
209
+ | `requestTimeout` | `number` | `20000` | 请求默认超时;方法级 `timeout > 0` 时覆盖此值。 |
210
+ | `requestMetadata` | `object \| (() => object)` | 可选 | TLV v1 只发送 `trace`、`sent_ms`。禁止放 credential、SecretKey 等敏感字段。SharedWorker 仅接受静态对象。 |
211
+ | `requestMetadataProvider` | `() => object` | 可选 | `requestMetadata` 函数形式的兼容选项;SharedWorker 不支持函数。 |
212
+ | `webSocketFactory` | `(url) => RTMWebSocket` | Node 必填 | 自定义 WebSocket 工厂。浏览器通常使用全局 `WebSocket`。 |
213
+ | `authSec` / `authVer` | `number` / `2` | 可选 | 自签鉴权字段;须同时提供,且 `authVer` 必须为 `2`。 |
214
+ | `locale` / `clientVersion` | `string` | 可选 | 登录语言和客户端版本。未提供版本时 SDK 使用内置版本字符串。 |
215
+ | `attrs` | `Record<string, string>` | 可选 | 登录连接属性。 |
216
+ | `tlvHeartbeatIdleMs` | `number` | `30000` | TLV 空闲多久发送心跳。 |
217
+ | `tlvHeartbeatTimeoutMs` | `number` | `10000` | TLV 心跳应答等待上限。 |
218
+ | `pushDedupTtlMs` | `number` | `30000` | 可靠 Push 的本地去重窗口,实际下限 20 秒。 |
219
+ | `regressiveStrategy` | `{startConnectFailedCount, maxIntervalSeconds, linearRegressiveCount}` | `{3, 8, 4}` | 控制自动重连退避;分别表示开始线性退避的失败次数、最大间隔秒数和线性递增步数。 |
220
+
221
+ `requestMetadata` 与 `requestMetadataProvider` 任选一种即可。实现按 `requestMetadata || requestMetadataProvider` 取值:当 `requestMetadata` 为 truthy 时优先使用它,`requestMetadataProvider` 只作后备;两者同时提供时,前者生效。建议新接入统一使用 `requestMetadata`。
222
+
223
+ ### Callback、Promise 与错误
224
+
225
+ | 调用类型 | 成功 | 失败 |
18
226
  | --- | --- | --- |
19
- | `pid` | `number` | 必填。 |
20
- | `protocol` | `'tlv-v1' \| 'fpnn'` | TLV 新接入为 `'tlv-v1'`。 |
21
- | `tlvEndpoint` | `string` | TLV 必填完整 WebSocket URL。 |
22
- | `autoReconnect` | `boolean` | 缺省 `true`。 |
23
- | `connectionTimeout` | `number` | 连接超时。 |
24
- | `requestTimeout` | `number` | 请求超时。 |
25
- | `requestMetadata` / `requestMetadataProvider` | `() => Record<string, unknown>` | 可返回 `trace`、`sent_ms`。 |
26
- | `webSocketFactory` | `(url: string) => RTMWebSocket` | 非浏览器运行时的 WebSocket 工厂。 |
27
- | `authSec` / `authVer` | `number` | 自签鉴权参数;`authVer` 固定为 `2`。 |
28
- | `locale` | `string` | 登录语言。 |
29
- | `clientVersion` | `string` | 客户端版本。 |
30
- | `attrs` | `Record<string, string>` | 连接属性。 |
31
- | `tlvHeartbeatIdleMs` / `tlvHeartbeatTimeoutMs` | `number` | TLV 心跳参数。 |
32
- | `pushDedupTtlMs` | `number` | 可靠 Push 去重窗口。 |
33
- | `regressiveStrategy` | `RTMRegressiveStrategy` | 重连退避参数。 |
34
-
35
- `RTMWebSocket` 必须提供 `send(data)`、`close(code?, reason?)`、`onopen`、`onmessage`、
36
- `onerror`、`onclose`。
37
-
38
- ## 生命周期与连接
39
-
40
- | API | 回调 |
227
+ | `login` callback | `callback(true, 0)` | `callback(false, errorCode)`;TLV Gate 仅返回 `accepted: false` 而无错误码时 SDK 使用 `200027`。 |
228
+ | `promises.login` | resolve `{accepted: true, errorCode}` | reject 带 `code` 的 Error。 |
229
+ | 普通业务 callback | `callback(null, data)` | `callback(error, null)`;错误通常包含 `code`,TLV `control.error` 还含 `status`、`reason`。TLV 消息发送 API 的错误结构是例外,见下文。 |
230
+ | `client.promises.method(...原参数)` | resolve 对应 callback 的 `data` | reject 对应 callback 的错误。参数顺序相同,去掉 callback;不要向 Promise 方法传 callback。 |
231
+ | `loginIfNeeded` | resolve `{accepted, errorCode, reused, uid}` | RTC 鉴权结果仍 resolve;Worker 启动、调用参数或缺少 credential 等 SDK 错误 reject。 |
232
+
233
+ `ErrorRecorder(error)` 用于连接、协议、自动重连和登录错误,不会代替普通业务请求的 callback 或 Promise rejection。业务失败应检查各自调用结果。请求超时与连接中断会结束挂起调用;连接中断时写入结果可能未知,SDK 不自动重放,业务应按幂等规则决定是否重试。
234
+
235
+ `bye()` 与普通业务调用不同,是异步的协议关闭握手。优先使用有超时的 Promise,并始终在 `finally` 中 `destroy()`;不必等待 `SessionClosed` 才能清理:
236
+
237
+ ```js
238
+ try {
239
+ await client.promises.bye(5000);
240
+ } catch (error) {
241
+ console.warn('Gate did not confirm close', error);
242
+ } finally {
243
+ client.destroy();
244
+ }
245
+ ```
246
+
247
+ Promise 成功表示 Gate 已应答关闭请求。若要直接断开,可调用 `close()` / `destroy()`;它们可能中止尚未完成的 `bye()`。
248
+
249
+ 常用事件:`ErrorRecorder(error)`、`ReloginCompleted(successful, retryAgain, errorCode, retriedCount)`、`SessionClosed(errorCode)`。TLV 可靠 Push 由 SDK 自动 ACK,并按 `message_ref` 去重;这不等于离线补拉或业务存储。
250
+
251
+ ## 4. 同 UID 多窗口:SharedWorker
252
+
253
+ `RTMSharedWorkerClient` 是 SDK 提供的同源多页面连接复用能力。每个页面创建一个客户端端口,SDK 在 SharedWorker 内维护一个 `RTMClient` 和一条 WebSocket。普通浏览器脚本加载时,SDK 默认从主 SDK 脚本地址推导同目录下的 `rtm.shared-worker.js`;部署在其他位置时,需显式传入同源 `workerUrl`。Vite 项目推荐从 `@cg-devcenter/rtc-fpnn-webjs-sdk/vite` 导入,SDK 会自动把 Worker 作为构建资源并配置地址。其他构建工具可在其资源管线支持时导入 `@cg-devcenter/rtc-fpnn-webjs-sdk/shared-worker?url`,再将解析出的 URL 传给 `workerUrl`。共享身份需要相同浏览器配置文件、origin、解析后的 Worker URL、`workerName` 和结构一致的 `clientOptions`。一个 Worker 只登录一个 UID、使用一种协议;传入 `uid` 后 SDK 会根据协议、项目、UID、endpoint 和可选 `workerNamespace` 生成稳定名称。TLV 自动名称沿用此前规则,FPNN 名称单独隔离。
254
+
255
+ | API / 选项 | 参数与结果 |
41
256
  | --- | --- |
42
- | `login(uid, credential, callback, timeout)` | `callback(accepted, errorCode)` |
43
- | `bye()` | 无 |
44
- | `close()` | 无 |
45
- | `startAutoReconnect()` | 无 |
46
- | `destroy()` | 无 |
47
- | `getServerTime(timeout, callback)` | `callback(err, data)` |
48
- | `addAttrs(attrs, timeout, callback)` | `callback(err, data)` |
49
- | `getAttrs(timeout, callback)` | `callback(err, data)` |
50
- | `addDebugLog(msg, attrs, timeout, callback)` | `callback(err, data)` |
51
-
52
- 事件:`ErrorRecorder(error)`、`ReloginCompleted(successful, retryAgain, errorCode, retriedCount)`、
53
- `SessionClosed(errorCode)`。
54
-
55
- ## 用户、存储与黑名单
56
-
57
- - `getOnlineUsers(uids, timeout, callback)`
58
- - `setUserInfo(oinfo, pinfo, timeout, callback)`
59
- - `getUserInfo(timeout, callback)`
60
- - `getUserOpenInfo(uids, timeout, callback)`
61
- - `setTranslationLanguage(targetLanguage, timeout, callback)`
62
- - `dataSet(key, value, timeout, callback)`
63
- - `dataGet(key, timeout, callback)`
64
- - `dataDelete(key, timeout, callback)`
65
- - `addBlacks(blacks, timeout, callback)`
66
- - `deleteBlacks(blacks, timeout, callback)`
67
- - `getBlacks(timeout, callback)`
68
-
69
- ## 群组与房间
70
-
71
- ### 群组
72
-
73
- - `addGroupMembers(gid, uids, timeout, callback)`
74
- - `deleteGroupMembers(gid, uids, timeout, callback)`
75
- - `getGroupMembers(gid, online, timeout, callback)`
76
- - `getGroupCount(gid, online, timeout, callback)`
77
- - `getUserGroups(timeout, callback)`
78
- - `setGroupInfo(gid, oinfo, pinfo, timeout, callback)`
79
- - `getGroupInfo(gid, timeout, callback)`
80
- - `getGroupOpenInfo(gid, timeout, callback)`
81
- - `getGroupsOpenInfo(gids, timeout, callback)`
82
-
83
- ### 房间
84
-
85
- - `enterRoom(rid, timeout, callback)`
86
- - `enterRooms(rids, timeout, callback)`
87
- - `leaveRoom(rid, timeout, callback)`
88
- - `getUserRooms(timeout, callback)`
89
- - `getUserRoomsAndLastMessage(mtypes, timeout, callback)`
90
- - `getRoomMembers(rid, timeout, callback)`
91
- - `getRoomCount(rids, timeout, callback)`
92
- - `setRoomInfo(rid, oinfo, pinfo, timeout, callback)`
93
- - `setRoomProfile(rid, publicData, privateData, label, avatar, timeout, callback)`
94
- - `getRoomInfo(rid, timeout, callback)`
95
- - `getRoomOpenInfo(rid, timeout, callback)`
96
- - `getRoomsOpenInfo(rids, timeout, callback)`
97
-
98
- ## 消息
99
-
100
- ### 发送
101
-
102
- - `sendMessage(to, mtype, msg, attrs, mid, timeout, callback)`
103
- - `sendMessages(tos, mtype, msg, attrs, mid, timeout, callback)`
104
- - `sendGroupMessage(gid, mtype, msg, attrs, mid, timeout, callback)`
105
- - `sendRoomMessage(rid, mtype, msg, attrs, mid, timeout, callback)`
106
- - `sendChat(to, msg, attrs, mid, timeout, callback)`
107
- - `sendAudio(to, msg, attrs, mid, timeout, callback)`
108
- - `sendCmd(to, msg, attrs, mid, timeout, callback)`
109
- - `sendGroupChat(gid, msg, attrs, mid, timeout, callback)`
110
- - `sendGroupAudio(gid, msg, attrs, mid, timeout, callback)`
111
- - `sendGroupCmd(gid, msg, attrs, mid, timeout, callback)`
112
- - `sendRoomChat(rid, msg, attrs, mid, timeout, callback)`
113
- - `sendRoomAudio(rid, msg, attrs, mid, timeout, callback)`
114
- - `sendRoomCmd(rid, msg, attrs, mid, timeout, callback)`
115
-
116
- 发送 API 的成功回调为 `callback(null, {message_ref, payload})`,其中 `payload` 为 Gate 的
117
- `{message_ref, time_ms}`;失败回调为 `callback({message_ref, error}, null)`。
118
-
119
- ### 查询、历史与撤回
120
-
121
- - `getMessage(from, mid, xid, type, timeout, callback)`
122
- - `deleteMessage(from, mid, xid, type, timeout, callback)`
123
- - `getChat(from, mid, xid, type, timeout, callback)`
124
- - `deleteChat(from, mid, xid, type, timeout, callback)`
125
- - `getP2PMessage(ouid, desc, num, begin, end, lastid, mtypes, timeout, callback)`
126
- - `getGroupMessage(gid, desc, num, begin, end, lastid, mtypes, timeout, callback)`
127
- - `getRoomMessage(rid, desc, num, begin, end, lastid, mtypes, timeout, callback)`
128
- - `getBroadcastMessage(desc, num, begin, end, lastid, mtypes, timeout, callback)`
129
- - `getP2PMessageByMessageId(ouid, mid, desc, num, begin, end, mtypes, timeout, callback)`
130
- - `getGroupMessageByMessageId(gid, mid, desc, num, begin, end, mtypes, timeout, callback)`
131
- - `getRoomMessageByMessageId(rid, mid, desc, num, begin, end, mtypes, timeout, callback)`
132
- - `getBroadcastMessageByMessageId(mid, desc, num, begin, end, mtypes, timeout, callback)`
133
- - `getP2PMessageCount(ouid, begin, end, mtypes, timeout, callback)`
134
- - `getGroupMessageCount(gid, begin, end, mtypes, timeout, callback)`
135
- - `getRoomMessageCount(rid, begin, end, mtypes, timeout, callback)`
136
- - `getBroadcastMessageCount(begin, end, mtypes, timeout, callback)`
137
- - `getP2PChat(ouid, desc, num, begin, end, lastid, timeout, callback)`
138
- - `getGroupChat(gid, desc, num, begin, end, lastid, timeout, callback)`
139
- - `getRoomChat(rid, desc, num, begin, end, lastid, timeout, callback)`
140
- - `getBroadcastChat(desc, num, begin, end, lastid, timeout, callback)`
141
-
142
- ## 未读与会话
143
-
144
- - `getUnreadMessage(clear, withLogout, timeout, callback)`
145
- - `getP2PUnreadMessageNum(uids, mtime, mtypes, timeout, callback)`
146
- - `getGroupUnreadMessageNum(gids, mtime, mtypes, timeout, callback)`
147
- - `cleanUnreadMessage(timeout, callback)`
148
- - `setSessionRead(category, xid, readSeq, timeout, callback)`
149
- - `getSession(timeout, callback)`
150
- - `removeSession(to, localOnly, timeout, callback)`
151
- - `getP2PConversationList(mtime, mtypes, timeout, callback)`
152
- - `getP2PUnreadConversationList(mtime, mtypes, timeout, callback)`
153
- - `getGroupConversationList(mtime, mtypes, timeout, callback)`
154
- - `getGroupUnreadConversationList(mtime, mtypes, timeout, callback)`
155
- - `getUnreadConversationList(clear, mtime, mtypes, timeout, callback)`
156
-
157
- ## 文本能力与 Push
158
-
159
- - `textCheck(text, strategyId, timeout, callback)`
160
- - `translate(originalMessage, originalLanguage, targetLanguage, type, profanity, timeout, callback)`
161
- - `client.processor.on(RTMConfig.TLV_SERVER_PUSH.recvMessage, callback)`
162
- - `client.processor.on(RTMConfig.TLV_SERVER_PUSH.recvGroupMessage, callback)`
163
- - `client.processor.on(RTMConfig.TLV_SERVER_PUSH.recvRoomMessage, callback)`
164
- - `client.processor.on(RTMConfig.TLV_SERVER_PUSH.recvBroadcastMessage, callback)`
165
- - `client.processor.on(RTMConfig.TLV_SERVER_PUSH.kickOut, callback)`
166
- - `client.processor.on(RTMConfig.TLV_SERVER_PUSH.kickOutRoom, callback)`
167
-
168
- TLV 未开放 API 返回 `200020`:文件、好友、设备 Push 和未迁移增值能力。
257
+ | `new RTMSharedWorkerClient(options)` | `clientOptions` 必填;`protocol` 省略时仍为 TLV v1,提供 `pid` 和完整 `tlvEndpoint`。选择 FPNN 时显式设置 `protocol: 'fpnn'`,并提供 `endpoint` 或 `ssl_endpoint`(`host[:port]`,不含 URL scheme 和路径)。`workerUrl` 可选,同源 Worker 脚本 URL;普通浏览器脚本省略时 SDK 推导加载 SDK 的脚本同目录下的 `rtm.shared-worker.js`,Vite 项目应使用 `/vite` 入口自动打包 Worker。提供 `uid` 且省略 `workerName` 时,SDK 根据协议、`clientOptions.pid`、`uid`、实际连接地址和 `workerNamespace` 生成稳定名称;也可调用 `createSharedWorkerName({protocol, pid, uid, endpoint, namespace})` 自行取得名称。若 `uid` 和 `workerName` 都省略,会使用通用默认名称;多账号应用应传其中一个,避免不同 UID 页面加入同一个 Worker。`readyTimeout` 默认 10000 毫秒;`idleShutdownTimeout` 默认 5000 毫秒,最后一个页面 detach 后等待这段时间再关闭共享连接,设为 `0` 表示立即关闭;同一 Worker 的所有页面必须使用相同值。`conversation` 可选,默认 `null` 接收全部会话 Push。 |
258
+ | `whenReady()` | 等 Worker 初始化并返回 `{status, uid}` 快照。初始化失败时 Promise reject,SDK 会自动 detach 并关闭当前页面的端口。 |
259
+ | `getSharedSession()` | 同步返回本页面最近观察到的 `{status, uid}`。 |
260
+ | `loginIfNeeded(uid, credential?, timeout?)` | 同 UID 会话已登录则复用;登录中则加入该登录;会话空闲时须提供服务端签发的 credential。resolve `{accepted, errorCode, reused, uid}`。 |
261
+ | `setConversation(conversation, callback?)` | 修改当前页面 Push 路由;传 `null` 接收全部会话。出错时通过可选 callback 返回。 |
262
+ | `destroy()` | detach 当前页面端口;Worker 仍有其他页面时不关闭 RTC 连接。它会完成端口与监听器清理后再传播待处理业务回调抛出的异常。 |
263
+ | `shared.promises.*` / `shared.*` | 可调用所选协议的 `RTMClient` 业务 API,参数、callback、返回对象和 Push 事件均沿用该协议;两种协议都不支持 `sendFile` / `sendGroupFile` / `sendRoomFile`。`bye()` / `close()` 影响所有共享页面。 |
264
+
265
+ 普通浏览器脚本部署时,把 `dist/rtm.min.js` 与 `dist/rtm.shared-worker.js` 放在同源静态目录的同一目录,SDK 会自动定位 Worker 文件。Vite 项目从包的 `/vite` 入口导入;SDK 会将 Worker 作为构建资源输出并自动配置地址,无需手动设置 `workerUrl`。其他构建工具需保证 Worker 最终同源可访问,并在自动推导不适用时显式传入 `workerUrl`。以下示例展示页面接入、首次登录、后续页面复用、Push 监听和当前页 detach。与快速开始相同,替换项目参数和 credential 接口:
266
+
267
+ ```js
268
+ const rtm = window.rtm;
269
+ const uid = '10001';
270
+ const credentialUrl = '/api/rtc/credential?uid=' + encodeURIComponent(uid);
271
+
272
+ async function getCredential() {
273
+ const response = await fetch(credentialUrl, { credentials: 'same-origin' });
274
+ if (!response.ok) throw new Error('Credential request failed: ' + response.status);
275
+ const result = await response.json();
276
+ if (!result.credential) throw new Error('Credential is missing');
277
+ return result.credential;
278
+ }
279
+
280
+ const shared = new rtm.RTMSharedWorkerClient({
281
+ uid, // SDK 用 PID、UID 和 endpoint 生成稳定的 Worker 名称;仅用于命名,不会自动登录。
282
+ clientOptions: {
283
+ protocol: 'tlv-v1',
284
+ pid: 12345,
285
+ tlvEndpoint: 'wss://<gate-host>/service/websocket',
286
+ autoReconnect: true,
287
+ },
288
+ conversation: null, // 收件箱接收所有会话;聊天页可只订阅一个会话。
289
+ });
290
+
291
+ function renderPush(event) { console.log('RTC Push', event); }
292
+ function renderSession(session) { console.log('Shared RTC session', session); }
293
+
294
+ [
295
+ rtm.RTMConfig.TLV_SERVER_PUSH.recvMessage,
296
+ rtm.RTMConfig.TLV_SERVER_PUSH.recvGroupMessage,
297
+ rtm.RTMConfig.TLV_SERVER_PUSH.recvRoomMessage,
298
+ rtm.RTMConfig.TLV_SERVER_PUSH.recvBroadcastMessage,
299
+ ].forEach((name) => shared.processor.on(name, renderPush));
300
+ shared.on('SharedSessionState', renderSession);
301
+
302
+ async function connectInbox() {
303
+ const snapshot = await shared.whenReady();
304
+ // 建议先让一个页面登录,再打开其余页面。已登录/正在登录时无需再取 credential。
305
+ const credential = snapshot.status === 'idle' ? await getCredential() : undefined;
306
+ const result = await shared.loginIfNeeded(uid, credential);
307
+ if (!result.accepted) throw Object.assign(new Error('RTM login rejected'), { code: result.errorCode });
308
+ console.log('Shared session ready', result);
309
+ }
310
+
311
+ connectInbox().catch(console.error);
312
+ // 当前页关闭时 SDK 会自动 detach(BFCache 页面例外);应用也可主动调用:
313
+ // shared.destroy();
314
+ ```
315
+
316
+ - 首个页面在 UID 尚未登录时传入服务端签发的 credential;其他同 UID 页面在已登录时不需要 credential。若它们赶在首次登录完成前打开,`loginIfNeeded()` 会加入同一次登录。
317
+ - `whenReady()` 返回 `{status, uid}`;`loginIfNeeded(uid, credential?, timeout?)` 返回 `{accepted, errorCode, reused, uid}`。credential 不会广播到页面。
318
+ - `conversation: {type: 'direct'|'group'|'room', id}` 只把匹配的私聊发送者、群或房间 Push 路由到该页面;`null` 接收全量会话 Push。运行中可调用 `setConversation(...)` 切换路由。
319
+ - `event.broadcast` 和 `event.kicked` 发给全部页面;`event.room_removed` 发给订阅该房间的页面。页面自己的消息监听器由 SDK 端口分发。
320
+ - `destroy()` 只 detach 当前页面;还有页面连接时共享连接继续运行。任一页面调用 `bye()` / `close()` 会结束所有页面共享的 RTC 会话。
321
+ - 最后一个页面 detach 后,SDK 默认等待 5 秒再销毁 RTMClient 和连接;宽限期内新页面接入会取消回收。Worker 被浏览器提前回收时,业务仍需重新连接并按登录快照决定是否重新获取 credential。
322
+ - SharedWorker 仅共享实时连接和 Push,不同步页面草稿、未读 UI 或消息列表,也不持久化登录态。它不跨 origin、浏览器配置文件或 InPrivate 窗口共享;不支持时 SDK 不静默降级为每页一条连接。
323
+ - `clientOptions` 必须可结构化克隆;不能包含 `requestMetadataProvider`、元数据函数或 `webSocketFactory`。SharedWorker 只接受静态 `requestMetadata` 对象。
324
+
325
+ ### FPNN 多窗口接入
326
+
327
+ 此扩展目前在源码和本地构建中提供;已发布的 `1.0.0-cg.3` SharedWorker 仍仅支持 TLV,使用 FPNN 共享模式需要新版本或本地打包产物。Node.js 仍只支持 TLV。
328
+
329
+ 首次登录、后续窗口 `loginIfNeeded()`、`SharedSessionState`、路由和 detach 的使用方式与上面的 TLV 示例相同。FPNN 只改变连接配置和 Push 订阅;以下片段假定已加载 `rtm` 并取得 `pid`、`uid`、`peerUid`、FPNN Gate 地址及首次登录所需的 credential:
330
+
331
+ ```js
332
+ const shared = new rtm.RTMSharedWorkerClient({
333
+ uid,
334
+ conversation: { type: 'direct', id: peerUid },
335
+ clientOptions: {
336
+ protocol: 'fpnn', // 显式选择;省略时仍是 TLV。
337
+ pid,
338
+ ssl_endpoint: 'gate.example:13321', // wss;ws 使用 endpoint: 'gate.example:13321'
339
+ autoReconnect: true,
340
+ },
341
+ });
342
+
343
+ // FPNN 文本聊天使用 recvChat,通用消息使用 recvMessage。
344
+ shared.processor.on(rtm.RTMConfig.SERVER_PUSH.recvChat, (event) => {
345
+ console.log('FPNN chat', event.from.toString(), event.msg);
346
+ });
347
+ shared.on('SharedSessionState', (state) => console.log('Shared session', state));
348
+ await shared.whenReady();
349
+ const result = await shared.loginIfNeeded(uid, credential);
350
+ if (!result.accepted) throw new Error('RTC login failed: ' + result.errorCode);
351
+ await shared.promises.sendChat(peerUid, 'hello', '{}');
352
+ // 页面离开时 SDK 自动 detach;也可显式 shared.destroy()。
353
+ ```
354
+
355
+ FPNN 的 `endpoint` / `ssl_endpoint` 接收 `host[:port]`,SDK 补上 `ws://` / `wss://` 和 `/service/websocket`;它与 TLV 的完整 URL 配置不同。自动 Worker identity 使用实际连接地址,同 UID 的 FPNN / TLV 页面不共享一条连接。若显式指定相同 `workerName` 却传入不同协议或配置,后加入页面初始化失败,不会覆盖已有会话。
356
+
357
+ FPNN Push 保留 `SERVER_PUSH` 事件和 `from / gid / rid / mid / msg` 等字段;TLV 保留 `TLV_SERVER_PUSH` 事件及其字段。共享层只提取内部会话路由,不把 FPNN 业务数据包装成 TLV,也不执行额外的 ACK 或去重。FPNN 的聊天、命令、通用消息、文件接收事件均按会话过滤;广播和被踢下线事件发给所有页面。`sendFile` / `sendGroupFile` / `sendRoomFile` 在两种模式下都本地拒绝,文件接收和 FPNN `fileToken` 查询不在此限制内。传统参数与 Push 细节见 [协议迁移对照](PROTOCOL_MIGRATION.md) 和 [事件处理说明](EventProcess.md)。
358
+
359
+ ## 5. 参数与返回值约定
360
+
361
+ 下表解释多个 API 复用的参数;详细 wire 字段、返回字段和 TLV 类型请参照[契约表](TLV_CONTRACT.md)。
362
+
363
+ | 参数 | 含义、类型和默认行为 |
364
+ | --- | --- |
365
+ | `uid` / `ouid` / `from` / `to` / `uids` | 用户 ID 或用户 ID 数组。可传十进制字符串、number 或 `RTMConfig.Int64`。大于 `Number.MAX_SAFE_INTEGER` 时必须用字符串或 `Int64`。 |
366
+ | `gid` / `rid` / `gids` / `rids` | 群组、房间 ID 或数组;同样建议大 ID 使用字符串或 `Int64`。 |
367
+ | `mid` | 消息引用 ID,不是历史分页游标。发送 API 可省略以便 SDK 生成;按消息 ID 查询时表示锚点。 |
368
+ | `timeout` | 单次请求超时,正数毫秒;省略或不为正数时使用 `requestTimeout`(默认 20 秒)。连接握手另由 `connectionTimeout` 控制。 |
369
+ | `msg` / `payload` | 消息内容,TLV v1 接受字符串或二进制。 |
370
+ | `attrs` | 消息附加信息字符串,映射为 `extra`;TLV 要求此字段存在,无附加信息时传 `'{}'`。 |
371
+ | `mtype` / `mtypes` | 消息类型整数或类型数组。常用值见 `RTMConfig.CHAT_TYPE`;省略 `mtypes` 表示不按类型筛选。 |
372
+ | `begin` / `end` | 历史查询起止时间,Unix epoch 毫秒;省略时 SDK 发 `0`。上下界包含规则由 Gate 定义,本文不额外推断。 |
373
+ | `desc` | 布尔值,映射为历史查询方向 `reverse`;`true` 从 `end` 开始倒序查询,`false` 从 `begin` 开始顺序查询;默认 `false`。 |
374
+ | `num` | 单页消息数,映射为 `limit`;默认 `20`。 |
375
+ | `lastid` | 历史翻页 cursor,非消息 ID 或时间戳;默认 `0`。把上一页结果的 `next` 作为下一页的 `lastid`。 |
376
+ | `mtime` | 会话/未读筛选起点,Unix epoch 毫秒,映射为 `since_ms`;省略表示不提供该筛选条件。 |
377
+ | `category` / `type` | 会话或单条消息所在的通道编号。类型值及各 API 允许范围见 TLV 契约;不要把它当成消息类型。 |
378
+ | `xid` | 与 `category` 对应的目标 ID,例如私聊 UID 或群/房间 ID。 |
379
+ | `readSeq` | `setSessionRead` 要标为已读的消息序号上界,正整数 int64。 |
380
+ | `clear` / `localOnly` / `online` | 布尔开关;分别控制查询时标记已读、本地删除会话、是否查询在线成员。未读摘要的 `clear` 默认 `false`;`removeSession` 的 `localOnly` 默认 `false`。 |
381
+
382
+ ### 构造、账号和存储
383
+
384
+ 下表业务 callback 方法以 `callback(err, data)` 结尾;`login` 和 `bye` 的回调形式见上文。表中省略重复的 callback 参数。Promise 版本保留业务参数顺序,仅省略 callback。
385
+
386
+ | API | 参数 | TLV v1 成功返回 |
387
+ | --- | --- | --- |
388
+ | `getOnlineUsers(uids, timeout)` | `uids`: 用户 ID 数组 | `RTMConfig.Int64[]` 在线 UID。 |
389
+ | `setUserInfo(oinfo, pinfo, timeout)` | `oinfo` 公共资料字符串;`pinfo` 私有资料字符串,均可省略 | 空 data。 |
390
+ | `getUserInfo(timeout)` | 无 | `{public_data, private_data}`。 |
391
+ | `getUserOpenInfo(uids, timeout)` | 用户 ID 数组 | `profiles` 用户资料映射。 |
392
+ | `setTranslationLanguage(targetLanguage, timeout)` | 目标语言字符串 | `{updated}`。 |
393
+ | `dataSet(key, value, timeout)` / `dataGet(key, timeout)` / `dataDelete(key, timeout)` | `key` 存储键;`value` 字符串 | set/delete 空 data;get 返回 `{value}`。 |
394
+ | `addBlacks(blacks, timeout)` / `deleteBlacks(blacks, timeout)` | `blacks`: 用户 ID 数组,TLV 最多 100 项 | 空 data。 |
395
+ | `getBlacks(timeout)` | 无 | `RTMConfig.Int64[]` 黑名单 UID。 |
396
+
397
+ ### 群组和房间
398
+
399
+ | API | 参数 | TLV v1 成功返回 |
400
+ | --- | --- | --- |
401
+ | `addGroupMembers(gid, uids, timeout)` / `deleteGroupMembers(gid, uids, timeout)` | 群 ID、成员 UID 数组 | 空 data。 |
402
+ | `getGroupMembers(gid, online, timeout)` | `online` 可选,是否附带在线成员 | `{members, active_members?}`。 |
403
+ | `getGroupCount(gid, online, timeout)` | `online` 可选,是否返回在线人数 | `{total, active_total?}`。 |
404
+ | `getUserGroups(timeout)` | 无 | 群 ID 数组 `RTMConfig.Int64[]`。 |
405
+ | `setGroupInfo(gid, oinfo, pinfo, timeout)` | 群 ID;公共/私有资料字符串,可省略 | 空 data。 |
406
+ | `getGroupInfo(gid, timeout)` / `getGroupOpenInfo(gid, timeout)` | 群 ID | 完整资料 `{public_data, private_data}` 或公开资料 `{public_data}`。 |
407
+ | `getGroupsOpenInfo(gids, timeout)` | 群 ID 数组 | `profiles` 映射。 |
408
+ | `enterRoom(rid, timeout)` / `leaveRoom(rid, timeout)` | 房间 ID | 空 data。 |
409
+ | `enterRooms(rids, timeout)` | 房间 ID 数组,TLV 最多 100 项 | 空 data。 |
410
+ | `getUserRooms(timeout)` | 无 | 房间 ID 数组 `RTMConfig.Int64[]`。 |
411
+ | `getRoomMembers(rid, timeout)` / `getRoomCount(rids, timeout)` | 房间 ID 或 ID 数组 | `{members}` / `{totals}`。 |
412
+ | `setRoomInfo(rid, oinfo, pinfo, timeout)` | 房间 ID;公共/私有资料字符串 | 空 data。 |
413
+ | `setRoomProfile(rid, publicData, privateData, label, avatar, timeout)` | 扩展写入方法;可同时设置公共/私有资料、房间名称和头像。 | 空 data。 |
414
+ | `getRoomInfo(rid, timeout)` / `getRoomOpenInfo(rid, timeout)` / `getRoomsOpenInfo(rids, timeout)` | 房间 ID 或数组 | 完整资料、单房间公开资料或 `profiles` 映射。 |
415
+ | `getUserRoomsAndLastMessage(mtypes, timeout)` | 可选消息类型数组 | `{last_by_room}`,value 为历史 `tuple8`。 |
416
+
417
+ ### 消息发送和单条消息
418
+
419
+ `sendChat`、`sendAudio`、`sendCmd` 分别固定文本、语音、命令消息类型;`sendGroup*`、`sendRoom*` 对应群和房间。发送成功 resolve/callback data 为 `{message_ref, payload: {message_ref, time_ms}}`。
420
+
421
+ TLV v1 的 `sendMessage`、`sendMessages`、`sendGroupMessage`、`sendRoomMessage` 及其快捷方法,失败时 callback 收到 `callback({message_ref, error}, null)`;Promise reject 的也是这个外层对象。协议错误码在内层 `error.code`,因此调用方应读取 `sendError.error.code`,不是 `sendError.code`:
422
+
423
+ ```js
424
+ try {
425
+ await client.promises.sendChat(peerUid, 'Hello', '{}');
426
+ } catch (sendError) {
427
+ console.error(sendError.message_ref, sendError.error.code);
428
+ }
429
+ ```
430
+
431
+ | API | 参数 |
432
+ | --- | --- |
433
+ | `sendMessage(to, mtype, msg, attrs, mid, timeout)` | 单个接收 UID、消息类型、正文、附加信息、可选消息引用。 |
434
+ | `sendMessages(tos, mtype, msg, attrs, mid, timeout)` | 接收 UID 数组(TLV 最多 100 项),其余同上。 |
435
+ | `sendGroupMessage(gid, mtype, msg, attrs, mid, timeout)` / `sendRoomMessage(rid, mtype, msg, attrs, mid, timeout)` | 群/房间 ID,其余同上。 |
436
+ | `sendChat(to, msg, attrs, mid, timeout)`、`sendAudio(to, msg, attrs, mid, timeout)`、`sendCmd(to, msg, attrs, mid, timeout)` | 私聊快捷方法;省略通用 `mtype`。 |
437
+ | `sendGroupChat(gid, msg, attrs, mid, timeout)`、`sendGroupAudio(gid, msg, attrs, mid, timeout)`、`sendGroupCmd(gid, msg, attrs, mid, timeout)` | 群消息快捷方法。 |
438
+ | `sendRoomChat(rid, msg, attrs, mid, timeout)`、`sendRoomAudio(rid, msg, attrs, mid, timeout)`、`sendRoomCmd(rid, msg, attrs, mid, timeout)` | 房间消息快捷方法。 |
439
+ | `getMessage(from, mid, xid, type, timeout)` / `getChat(...)` | 发送者 UID、消息引用、目标 ID、通道编号;`getChat` 是兼容别名。返回消息对象或空结果。 |
440
+ | `deleteMessage(from, mid, xid, type, timeout)` / `deleteChat(...)` | 同上;`deleteChat` 是兼容别名。成功返回空 data。 |
441
+
442
+ ### 历史、翻页与计数
443
+
444
+ `getP2PMessage`、`getGroupMessage`、`getRoomMessage`、`getBroadcastMessage` 参数顺序相同:`(target?, desc, num, begin, end, lastid, mtypes, timeout)`。target 分别为对方 UID、群 ID、房间 ID;广播查询没有 target。
445
+
446
+ | API | 参数变化 / 行为 |
447
+ | --- | --- |
448
+ | `getP2PMessage(ouid, desc, num, begin, end, lastid, mtypes, timeout)` | 私聊历史。 |
449
+ | `getGroupMessage(gid, desc, num, begin, end, lastid, mtypes, timeout)` | 群历史。 |
450
+ | `getRoomMessage(rid, desc, num, begin, end, lastid, mtypes, timeout)` | 房间历史。 |
451
+ | `getBroadcastMessage(desc, num, begin, end, lastid, mtypes, timeout)` | 广播历史。 |
452
+ | `getP2PMessageByMessageId(ouid, mid, desc, num, begin, end, mtypes, timeout)` | 私聊历史;以消息引用 `mid` 为锚点定位一页,无 `lastid` 参数。 |
453
+ | `getGroupMessageByMessageId(gid, mid, desc, num, begin, end, mtypes, timeout)` | 群历史锚点查询。 |
454
+ | `getRoomMessageByMessageId(rid, mid, desc, num, begin, end, mtypes, timeout)` | 房间历史锚点查询。 |
455
+ | `getBroadcastMessageByMessageId(mid, desc, num, begin, end, mtypes, timeout)` | 广播历史锚点查询。 |
456
+ | `getP2PChat(ouid, desc, num, begin, end, lastid, timeout)` | 私聊文本、语音、命令历史快捷方法。 |
457
+ | `getGroupChat(gid, desc, num, begin, end, lastid, timeout)` | 群文本、语音、命令历史快捷方法。 |
458
+ | `getRoomChat(rid, desc, num, begin, end, lastid, timeout)` | 房间文本、语音、命令历史快捷方法。 |
459
+ | `getBroadcastChat(desc, num, begin, end, lastid, timeout)` | 广播文本、语音、命令历史快捷方法。 |
460
+ | `getP2PMessageCount(ouid, begin, end, mtypes, timeout)` | 私聊消息数量。 |
461
+ | `getGroupMessageCount(gid, begin, end, mtypes, timeout)` | 群消息数量。 |
462
+ | `getRoomMessageCount(rid, begin, end, mtypes, timeout)` | 房间消息数量。 |
463
+ | `getBroadcastMessageCount(begin, end, mtypes, timeout)` | 广播消息数量。 |
464
+
465
+ TLV v1 历史页返回 `{size, items, next, first_ms, last_ms}`。`items` 每项为 `tuple8`:`[cursor_id, sender_or_direction, kind, message_ref, deleted, payload, extra, time_ms]`。`cursor_id` 是不透明翻页游标,不是 `message_ref`;将 `next` 原样传回 `lastid`。Anchor 查询的 `mid` 才是消息引用。时间字段用毫秒;不要用返回的 `cursor_id` 当时间戳或消息 ID。
466
+
467
+ 私聊历史翻页示例:假设 `client` 已登录、`peerUid` 是要查询的对方 UID,并且第一页确有后续页。首次查询使用 `lastid = 0`;下一次调用把第一页的 `next` 原样传回:
468
+
469
+ ```js
470
+ const rtm = window.rtm;
471
+
472
+ async function getDirectPage(client, peerUid, cursor) {
473
+ return client.promises.getP2PMessage(
474
+ peerUid, false, 20, 0, 0, cursor, [rtm.RTMConfig.CHAT_TYPE.text], 12000,
475
+ );
476
+ }
477
+
478
+ async function readTwoDirectPages(client, peerUid) {
479
+ const firstPage = await getDirectPage(client, peerUid, 0);
480
+ // 此示例假设第一页有后续页;实际 UI 仅在 Gate 给出可用 continuation 时调用。
481
+ const nextPage = await getDirectPage(client, peerUid, firstPage.next);
482
+ return [firstPage, nextPage];
483
+ }
484
+ ```
485
+
486
+ ### 未读、会话与 Push
487
+
488
+ | API | 参数 | TLV v1 成功返回 / 用途 |
489
+ | --- | --- | --- |
490
+ | `getUnreadMessage(clear, withLogout, timeout)` | 两个布尔参数可选;`clear` 默认 false,表示是否标记已读;`withLogout` 请求附带登出时间 | `{peers, group_refs, logout_sec?}`。 |
491
+ | `getP2PUnreadMessageNum(uids, mtime, mtypes, timeout)` / `getGroupUnreadMessageNum(gids, mtime, mtypes, timeout)` | ID 数组;可选起始毫秒和消息类型过滤 | `{counts, times}`。 |
492
+ | `cleanUnreadMessage(timeout)` | 无 | 清理未读状态,返回空 data。 |
493
+ | `setSessionRead(category, xid, readSeq, timeout)` | 会话通道编号、目标 ID、已读序号上界 | 设置会话已读位置,返回空 data。 |
494
+ | `getSession(timeout)` | 无 | `{peers, group_refs}` 会话 ID 集合。 |
495
+ | `removeSession(to, localOnly, timeout)` | 对方 UID;`localOnly` 默认 false | 删除会话,返回空 data。 |
496
+ | `getP2PConversationList(mtime, mtypes, timeout)` / `getP2PUnreadConversationList(mtime, mtypes, timeout)` | 可选毫秒筛选起点和消息类型 | `{peers, badges, last_items}`。 |
497
+ | `getGroupConversationList(mtime, mtypes, timeout)` / `getGroupUnreadConversationList(mtime, mtypes, timeout)` | 同上 | `{group_refs, badges, last_items}`。 |
498
+ | `getUnreadConversationList(clear, mtime, mtypes, timeout)` | `clear` 默认 false;可选筛选 | 合并私聊与群的 `peers/group_refs`、未读数和最后消息字段。 |
499
+
500
+ Push 用 `client.processor.on(name, callback)` 订阅:`RTMConfig.TLV_SERVER_PUSH.recvMessage`、`recvGroupMessage`、`recvRoomMessage`、`recvBroadcastMessage`、`kickOut`、`kickOutRoom`。各事件字段见[Push 契约](TLV_CONTRACT.md#控制-uri-与-push)。
501
+
502
+ ### 文本服务
503
+
504
+ | API | 参数 | TLV v1 能力 |
505
+ | --- | --- | --- |
506
+ | `textCheck(text, strategyId, timeout)` | 待审核文本、可选策略 ID | 支持;返回审核 decision、filtered 文本、识别语言及命中信息。 |
507
+ | `translate(originalMessage, originalLanguage, targetLanguage, type, profanity, timeout)` | 原文、可选源语言、目标语言、格式类型、过滤方式 | 支持;具体值域和返回字段见 TLV 契约。 |
508
+
509
+ ## 6. TLV v1 API 支持矩阵
510
+
511
+ 以下“支持”表示 SDK 有 TLV v1 URI 和字段契约,不代表当前 Gate 项目一定开通对应服务权限。旧 API 名称仍存在于 JavaScript 类中,不表示 TLV Gate 支持它。
512
+
513
+ | 功能 | TLV v1 API | TLV v1 | SharedWorker | 边界 / 替代方式 |
514
+ | --- | --- | --- | --- | --- |
515
+ | 登录、连接与系统 | `login`、`bye`、`getServerTime`、`addAttrs`、`getAttrs`、`addDebugLog`、重连与 Push 事件 | 支持 | 支持 | SharedWorker 支持 TLV 与显式选择的 FPNN;本表 SharedWorker 列描述 TLV 模式。 |
516
+ | 用户、资料与在线状态 | `getOnlineUsers`、`setUserInfo`、`getUserInfo`、`getUserOpenInfo`、`setTranslationLanguage` | 支持 | 支持 | 用户可见资料的权限由 Gate 项目控制。 |
517
+ | 用户存储与黑名单 | `dataSet`、`dataGet`、`dataDelete`、`addBlacks`、`deleteBlacks`、`getBlacks` | 支持 | 支持 | TLV 黑名单批量每次最多 100 个 UID。 |
518
+ | 群组 | `addGroupMembers`、`deleteGroupMembers`、`getGroupMembers`、`getGroupCount`、`getUserGroups`、`setGroupInfo`、`getGroupInfo`、`getGroupOpenInfo`、`getGroupsOpenInfo` | 支持 | 支持 | 服务端权限和群组存在状态仍由 Gate 校验。 |
519
+ | 房间 | `enterRoom`、`enterRooms`、`leaveRoom`、`getUserRooms`、`getUserRoomsAndLastMessage`、`getRoomMembers`、`getRoomCount`、资料读写 API | 支持 | 支持 | `enterRooms` 批量最多 100 个房间 ID。 |
520
+ | 消息发送和 Push | 私聊、群、房间、批量私聊发送 API;direct/group/room/broadcast Push | 支持 | 支持 | `sendMessages` 每次最多 100 个 UID;可靠 Push 自动 ACK 和去重。 |
521
+ | 历史、单条消息和计数 | `get*Message`、`get*MessageByMessageId`、`get*MessageCount`、`getMessage`、`deleteMessage` 及 Chat 别名 | 支持 | 支持 | 翻页 cursor 和消息引用是不同 ID;详见参数与返回约定。 |
522
+ | 未读与会话 | 未读数、会话列表、标记已读、删除会话 API | 支持 | 支持 | 不包含 SDK 外业务系统的未读规则和持久化 UI 状态。 |
523
+ | 文本审核与翻译 | `textCheck`、`translate` | 支持 | 支持 | 依赖 Gate 项目服务配置与权限。 |
524
+ | 文件消息 | `fileToken`、`sendFile`、`sendGroupFile`、`sendRoomFile` | 不支持 | 不支持 | TLV v1 Client port 未开放;需使用 FPNN 兼容入口或业务侧上传/转发方案。 |
525
+ | 好友关系 | `addFriends`、`deleteFriends`、`getFriends` | 不支持 | 不支持 | TLV v1 未开放。 |
526
+ | 设备 Push 设置 | `addDevice`、`removeDevice`、`addDevicePushOption`、`removeDevicePushOption`、`getDevicePushOption` | 不支持 | 不支持 | TLV v1 未开放。 |
527
+ | 增值与多媒体审核 | `profanity`、`imageCheck`、`audioCheck`、`videoCheck`、`speech2Text` | 不支持 | 不支持 | TLV v1 未开放;`textCheck` 和 `translate` 不在此限制内。 |
528
+
529
+ TLV 不支持的调用按实现分为三类:
530
+
531
+ - TLV 合约中保留、但没有 Client API 路由的 wire(如 `filetoken`、`addfriends`、`acheck`)返回 `200020`(`RTM_EC_FORBIDDEN_METHOD`)。
532
+ - 未登记且非保留的 wire 返回 `20004`(`RTM_EC_UNKNOWN_METHOD`)。
533
+ - `RTMSharedWorkerClient` 的 `sendFile`、`sendGroupFile`、`sendRoomFile` 在本地拒绝;callback 收到 `{mid, error}`,其中 `error.code` 为 `200999`(`RTM_EC_UNKNOWN_ERROR`);Promise 版本 reject 同一个错误对象,不会发送 wire 请求。
534
+
535
+ 这些错误都不会自动切换到 FPNN。SharedWorker 的 TLV 模式复用 TLV 支持范围;FPNN 模式可转发其兼容业务 API(文件发送除外)。两种模式都不接受函数型配置;`clientOptions` 必须可结构化克隆。FPNN 兼容能力列表见[能力总览](CAPABILITIES.md),服务端支持仍需单独确认。
536
+
537
+ ## 7. 调试与参考
538
+
539
+ - `requestMetadata` 可为请求附加 `trace`、`sent_ms` 以关联服务端日志,不要放 credential、项目 SecretKey 或个人敏感数据。
540
+ - `ErrorRecorder` 观察连接、协议、重连和登录错误;普通 API 错误通过该 API 的 callback / Promise 返回。
541
+ - [能力总览与业务流程](CAPABILITIES.md)
542
+ - [FPNN 与 TLV v1 迁移对照](PROTOCOL_MIGRATION.md)
543
+ - [TLV v1 URI、参数类型与返回字段](TLV_CONTRACT.md)
544
+ - [TLV v1 frame 与传输规则](TLV.md)
545
+ - [在线 Gate 验证说明](TLV_LIVE_E2E_TEST.md)
546
+ - [TypeScript API 声明](../dist/rtm.d.ts)