vanshi-sdk 1.0.6 → 1.2.26
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/LOCAL_STORAGE.md +166 -0
- package/README.md +190 -8
- package/dist/index.cjs +3869 -274
- package/dist/index.d.ts +3033 -798
- package/dist/index.js +32559 -3461
- package/dist/index.mjs +3873 -271
- package/package.json +12 -4
package/LOCAL_STORAGE.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# SDK 本地聊天记录
|
|
2
|
+
|
|
3
|
+
浏览器端使用 Dexie / IndexedDB。传入 `userId` 开启,不传则保持原有在线行为。
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
await VanshiSdk.create();
|
|
7
|
+
await VanshiSdk.login({ token, userId: '10001' });
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`userId` 不上传给登录接口,必须由业务保证与 Token 对应。当前服务端登录仅返回成功标记,SDK 无法校验账号映射。数据库按 Socket/HTTP 环境和 userId 隔离;浏览器同时按 origin 隔离。同一 origin、相同环境下多个应用应使用不冲突的用户命名空间。
|
|
11
|
+
|
|
12
|
+
登录、退出、重连恢复和 Token 续期绑定当前会话代次。换账号或退出会取消旧的排队请求和等待回包,旧登录结果不能修改新账号 Token 或数据库。传入 `userId` 时,登录完成前收到的消息推送先保存独立快照,数据库打开后按顺序入库,登录失败或被新登录替换则丢弃该次缓冲。登录期间普通请求等待认证结束;不自动拉取历史。SDK 不能撤销已送达服务端的旧请求,服务端仍需验证鉴权和幂等。
|
|
13
|
+
|
|
14
|
+
登录期间主动调用 `queryHistoryMessage`,会等待认证并使用就绪后的账号数据库合并本地发送中和失败消息;尚未创建连接时调用 `login` 会及时返回连接错误。页面一旦建立或清除会话,后续重连不会读取其他标签页后来写入的 Token;本页退出仅清除自身持有的持久化 Token,不删除其他账号标签页的新 Token。
|
|
15
|
+
|
|
16
|
+
## 查询记录和失败消息
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
const page = await VanshiSdk.queryLocalMessages({
|
|
20
|
+
conversationID: 'group-1',
|
|
21
|
+
conversationType: 2,
|
|
22
|
+
count: 30,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const nextPage = page.hasMore
|
|
26
|
+
? await VanshiSdk.queryLocalMessages({
|
|
27
|
+
conversationID: 'group-1',
|
|
28
|
+
conversationType: 2,
|
|
29
|
+
count: 30,
|
|
30
|
+
before: page.nextCursor,
|
|
31
|
+
})
|
|
32
|
+
: undefined;
|
|
33
|
+
|
|
34
|
+
// 不传会话条件可查询当前账号全部失败/结果未知的消息。
|
|
35
|
+
const failed = await VanshiSdk.queryFailedMessages({
|
|
36
|
+
conversationID: 'group-1',
|
|
37
|
+
conversationType: 2,
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
返回 `{ messageList, hasMore, nextCursor }`。消息包含 `localID`、`clientBizId`、`messageID`、`sendState`、`errorCode`、`errorMessage`。会话类型在数据库中统一为字符串。分页按 timestamp、localID 排序,同毫秒消息不会跳过;服务端 orderKey 原样保留,不做可能丢精度的 Number 转换。
|
|
42
|
+
|
|
43
|
+
`sendState`:
|
|
44
|
+
|
|
45
|
+
- `0`:发送中,也包括超时、断线或页面中断后等待服务端确认。
|
|
46
|
+
- `1`:发送成功。
|
|
47
|
+
- `2`:发送失败,包括服务端返回失败、媒体预处理或上传失败。
|
|
48
|
+
|
|
49
|
+
`sendState` 只返回数字 `0 | 1 | 2`。现有数据库首次打开时自动升级,将旧字符串状态转为数字,保留消息身份、正文和附件。`queryFailedMessages` 同时返回状态 `2` 和因超时、断线、页面中断而等待确认的记录,不包含正常发送中的记录。可用 `queryLocalMessages({ sendStates: [2] })` 仅查明确失败。等待确认时 `sendState` / `sentStatus` 保持 `0`,后续推送可更新同一条记录。Web 端保留等待 60 秒后显示失败的界面逻辑;SDK 的 ACK 超时不提前触发失败显示。
|
|
50
|
+
|
|
51
|
+
开启本地存储后,`queryHistoryMessage` 查询最新一页(不传 `config.nextMessage`)时,会合并当前会话本地待发送、发送中、失败和结果未知的消息,并补入本地已收到、位于本次服务端时间窗口内或更新的消息;服务端返回空页时保留本地可见记录。发送确认后按 messageID/clientBizId/localID 去重。合并结果按 timestamp 排序,同毫秒优先比较有效的十进制 orderKey(保留大整数精度),再按稳定消息 ID 排序;方向沿用服务端页面时间顺序,无法判断时按 reverse(默认倒序)。`sentStatus` 为 `0` 表示发送中,`1` 表示已确认,`2` 仅表示明确失败,结果未知仍为 `0`。文本和媒体消息的 `onMessageAttached` 在入库完成后触发,可在回调中查询。
|
|
52
|
+
|
|
53
|
+
历史查询成功后,先将回包写入本地数据库再返回合并结果。服务端 `sentStatus` 原样保留,并优先据此将 `sendState` 更新为数字 `0` / `1` / `2`,覆盖此前缓存的发送状态。兼容服务端返回字符串形式的 `sentStatus`,但 `sendState` 始终为数字。回包省略 `sentStatus` 时使用有效的 `sendState`,两者均未提供时按成功处理。回包带 `sentStatus: 2` 的消息可通过 `queryFailedMessages` 查询。较早启动的历史请求晚返回时,不覆盖较新查询、推送或发送成功回包已经更新的发送状态;后续服务端状态推送也可修正此前缓存的成功状态。
|
|
54
|
+
|
|
55
|
+
`config.count` 仍控制服务端分页条数,合并本地消息后返回条数可能超过 count。旧页不会重复追加未发送成功的消息。未确认消息的 `messageID` 为空,使用 `localID` 或 `clientBizId` 标识;远程翻页锚点应选取有服务端 messageID 的记录。本地分页使用 `queryLocalMessages` 的 `nextCursor`。`queryHistoryMessage` 仍需要远程请求成功,离线读取请使用 `queryLocalMessages`。
|
|
56
|
+
|
|
57
|
+
启用数据库时,已有 `searchLocalMessages(conversationID, conversationType, config)` 改为查询真正的本地库;未启用时保留原服务端调用。关键词、类型、发送者、时间范围可筛选;自定义子类型过滤暂未实现。
|
|
58
|
+
|
|
59
|
+
## 发送、重试和附件
|
|
60
|
+
|
|
61
|
+
文本、媒体、回复消息发送前自动入库。媒体预处理前保存原始附件 Blob,上传失败或刷新后仍可查询。成功确认后释放数据库中的附件原始文件。Blob 预览 URL 不持久化,展示恢复的附件时需重新 `URL.createObjectURL(row.file)`,用完后释放。
|
|
62
|
+
|
|
63
|
+
文本和媒体发送成功响应使用同一份规范化结果返回业务并入库:补齐本次预发 `clientBizId`,`sendState` 为数字;服务端明确给出 `sentStatus` 时原样保留并据此确定状态,省略状态时按成功处理。成功包络不覆盖明确的消息发送状态。回复接口保留既有 `code: 0` 确认成功与原消息身份保护规则。
|
|
64
|
+
|
|
65
|
+
视频发送时,`onMessageAttached` 先返回带发送者身份、`sentStatus: 0` / `sendState: 0` 的本地消息;首帧在本地解码后、上传前,以同一 `clientBizId` / `localMessageID` 和时间再次触发回调,补齐缩略图、宽高和时长。业务应按该 ID 更新同一条消息。视频地址不再用作图片缩略图。首帧以 `thumbnailFile` Blob 保存,发送失败或超时后仍可查询;`queryHistoryMessage` 为本地视频和首帧重建当前页面的预览 URL,切换账号或关闭数据库时释放这些查询预览 URL。消息成功、删除或撤回时清理首帧 Blob。回调中的预览 URL 由业务在不再展示时释放。
|
|
66
|
+
|
|
67
|
+
`replyMessage` 与 `sendMessage` 共用预发 clientBizId 生成逻辑。`onMessageAttached` 在请求发出前收到带 `clientBizId`、`localMessageID`、会话信息、时间和 `sentStatus: 0` 的消息;启用数据库时回调在入库完成后触发。请求、发送结果和失败记录使用相同 clientBizId,服务端回包省略该字段时 SDK 会补齐。独立传入的 clientBizId 会保留,可用于失败重试;从原消息复制来的身份字段会清除,原消息记录保持独立。
|
|
68
|
+
|
|
69
|
+
回复请求会排除 `fileLocalPath`、`file`、`thumbnailFile`、本地预览地址和存储元数据,避免回复图片或视频时再次附带本地二进制文件;消息 ID、远程文件地址和引用信息保留。此过滤只用于发送请求,不修改原消息、预发回调或本地数据库中的预览文件;自定义消息中明确传入的二进制 `message` 仍正常发送。
|
|
70
|
+
|
|
71
|
+
回复的预发消息同时包含 `sendState: 0` 和完整 `repliedInfo`(原消息 ID、发送者、时间和正文摘要),可立即展示“发送中的回复”,无需等服务端回包。发送失败仍保留引用信息;重试时清除旧错误并回到发送中。成功回包补齐服务端消息 ID,保持预发 `clientBizId` / `localMessageID`,更新同一条本地消息为 `1`,服务端省略引用字段时保留本地引用。
|
|
72
|
+
|
|
73
|
+
`replyMessage` 以服务端回包 `code: 0`(兼容字符串 `"0"`)作为发送成功依据,不会因回包缺少新消息 ID、回显原消息 ID 或引用自身而抛出 `INVALID_REPLY_RESPONSE`。回包有独立的新消息 ID 时正常补齐;回显原消息时,SDK 使用本次预发的正文、类型、引用和 `clientBizId` / `localMessageID`,更新同一条回复为 `sendState: 1` / `sentStatus: 1`,不会把原消息的 ID、图片数据或时间写进新回复,也不会修改原消息。
|
|
74
|
+
|
|
75
|
+
尚未获取新服务端 ID 的成功回复保持 `messageID` 为空,以本次 `clientBizId` 标识,不会伪造服务端 ID;可通过 `queryLocalMessages` 或最新一页 `queryHistoryMessage` 查询,且不属于失败消息。后续携带新 messageID 和相同 clientBizId 的推送或历史记录会补齐同一条本地回复,不重复生成。需要服务端 messageID 的后续操作应等待该 ID 补齐。原消息回显的自引用推送不覆盖原消息,历史查询仍保留已缓存的原消息。SDK 不会因此重发消息或主动重新登录;非零失败码和网络错误仍按原错误流程处理。
|
|
76
|
+
|
|
77
|
+
使用原 `clientBizId` 重试:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
await VanshiSdk.sendMessage({
|
|
81
|
+
toConversationID: failedMessage.conversationID,
|
|
82
|
+
conversationType: Number(failedMessage.conversationType),
|
|
83
|
+
message: {
|
|
84
|
+
type: String(failedMessage.type),
|
|
85
|
+
message: failedMessage.message,
|
|
86
|
+
extendedData: failedMessage.extendedData || '',
|
|
87
|
+
clientBizId: failedMessage.clientBizId,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
媒体消息使用 `sendMediaMessage` 并传入查询结果保留的 `file`。SDK 不会自动重发;服务端必须提供 clientBizId 幂等才能安全重试结果未知的消息。重复推送、发送确认和历史数据按 messageID/clientBizId 合并,保持 localID 不变。
|
|
93
|
+
|
|
94
|
+
## 同步与更新通知
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
VanshiSdk.on('localMessagesChanged', () => {
|
|
98
|
+
// 重新执行 queryLocalMessages 更新界面。
|
|
99
|
+
});
|
|
100
|
+
VanshiSdk.on('localStorageError', ({ message }) => {
|
|
101
|
+
// 提示当前操作未可靠保存到本地。
|
|
102
|
+
});
|
|
103
|
+
VanshiSdk.on('localSyncStateChanged', (state) => {
|
|
104
|
+
// syncing / synced / failed,按会话展示同步状态。
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// 按需主动补齐指定会话历史。
|
|
108
|
+
await VanshiSdk.syncConversationMessages({
|
|
109
|
+
conversationID: 'group-1',
|
|
110
|
+
conversationType: 2,
|
|
111
|
+
});
|
|
112
|
+
// 刷新服务端会话列表,并同步当前账号已缓存及新发现的会话。
|
|
113
|
+
await VanshiSdk.syncLocalMessages();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`syncLocalMessages` 成功时继续返回会话同步结果数组。发现会话失败、分页达到上限、数据格式错误、账号切换或部分会话失败时,Promise 拒绝并抛出可序列化的 `LocalSyncError`;`results` 包含已成功的会话,`failures` 包含失败会话及原因,`fullyReconciled` 仍为 `false`。应使用 `try/catch` 处理,不能再将空数组或成功子集当作全量同步完成。失败时仍发送 `localSyncStateChanged`。
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { LocalSyncError } from 'vanshi-sdk'
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const results = await VanshiSdk.syncLocalMessages()
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (error instanceof LocalSyncError) {
|
|
125
|
+
console.log(error.results, error.failures)
|
|
126
|
+
} else {
|
|
127
|
+
throw error
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
数据库自动升级到第 3 版,增加会话及会话状态索引。历史旧页按消息身份读取本地记录;最新页读取时间窗口及本地未成功消息,不再无条件加载全部会话记录。全量同步从会话索引发现已缓存会话,不加载全部正文和附件。首次升级会构建索引;媒体较多时仍需留意存储空间和升级耗时。
|
|
133
|
+
|
|
134
|
+
- 登录时仅打开当前账号数据库并复用已有记录,刷新页面、重复登录和重连恢复均不自动请求会话列表或聊天历史。离线期间的历史需由业务按需调用 `queryHistoryMessage`、`syncConversationMessages` 或 `syncLocalMessages` 补齐。
|
|
135
|
+
- 远程历史、按 ID 查询、回复查询、编辑和回执查询结果自动回写。
|
|
136
|
+
- 接收、编辑、撤回、删除、发送状态和回执推送在 SDK 内部持久化,不依赖业务注册监听器。业务调用 `off` 不影响存储。
|
|
137
|
+
- `receivePeerMessage` / `receiveGroupMessage` 收到的 `messageList` 自动写入当前账号数据库,按消息身份合并去重;不需要在回调里额外调用保存接口。未提供会话类型时按单聊 `0`、群聊 `2` 补齐;保留服务端 `sentStatus` 并同步数字 `sendState`,未提供状态时默认为 `1`。未提供方向时按发送者与当前账号推导,发送者也缺失时保留本地原有方向,新消息默认接收方向。仅传入登录参数 `userId` 开启本地数据库后生效。
|
|
138
|
+
- 接收、编辑、撤回、删除、发送状态和回执事件均在业务回调执行前保存数据快照,业务修改事件对象或抛异常不会改变保存内容。
|
|
139
|
+
- 重复的接收推送不能覆盖已经处理的编辑或回执更新,数据库重开后仍保留此保护;带更高编辑版本的接收推送仍可更新正文。版本不明时以独立编辑事件为准。撤回记录与旧历史合并时不会恢复已清除的附件地址。
|
|
140
|
+
- 本地查询等待此前已接收推送的存储任务;`localMessagesChanged` 在事务提交后发出。
|
|
141
|
+
- 未入库消息的延迟状态和回执补丁也参与请求时序比较,不用旧补丁覆盖之后主动查询的状态;撤回和删除墓碑继续保留。
|
|
142
|
+
- 消息成功、删除和撤回提交后,释放查询过程中为该消息创建的预览 URL;业务自行创建或预发回调中的 URL 仍由业务释放。
|
|
143
|
+
- 同一事件与函数重复 `on` 只注册一次,`off` 解除实际监听。普通消息、通话及本地事件逐个隔离同步异常和异步 Promise 拒绝,一个回调失败不阻断其他订阅者。
|
|
144
|
+
- 撤回记录保留占位并清除正文;删除记录保留墓碑,防止历史回填复活。
|
|
145
|
+
- 同账号标签页通过 BroadcastChannel 通知更新,没有自动发送任务,因此不需要跨页竞争自动重试。
|
|
146
|
+
- 退出账号关闭数据库但保留数据;切换账号后旧请求不能写入新账号库。
|
|
147
|
+
|
|
148
|
+
**一致性边界:** 当前服务端没有增量变更游标,离线期间删除的消息、未返回撤回标记的历史以及跨设备修改不能保证全部恢复。因此同步结果固定 `fullyReconciled: false`;`synced` 仅表示本次可用历史分页已拉取并写入,不能表示服务端与本地完全一致。每会话最多 100 页,每页 100 条,超出或游标不前进时返回失败,不会假报同步完成。服务器若有不同的分页上限,需要调整同步分页配置。同步接口由业务主动调用,仅刷新历史,不自动重发消息。
|
|
149
|
+
|
|
150
|
+
要实现严格的“始终最新”,服务端还需提供按账号游标返回创建、编辑、撤回、删除及回执变化的增量接口,并与本地事务共同提交游标。当前实现不通过“历史中没出现”判断删除,以免把未加载记录误删。
|
|
151
|
+
|
|
152
|
+
## 清理与降级
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
// 只清理当前会话本地展示记录,不调用服务端删除接口。
|
|
156
|
+
await VanshiSdk.clearLocalMessages({ conversationID: 'group-1', conversationType: 2 });
|
|
157
|
+
const status = VanshiSdk.getLocalStorageStatus();
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
清理会保留清理时间边界和墓碑,旧历史不会重新显示;不带参数清理当前账号全部本地消息。此接口不是彻底销毁数据库。远程删除接口成功时也会同步清理本地消息。
|
|
161
|
+
|
|
162
|
+
IndexedDB 打不开或写入失败时在线功能继续工作,并发出 localStorageError;调用方必须监听错误,不能假设全部消息已持久化。浏览器配额、隐私模式、清理站点数据可能影响持久性,媒体文件占用较大,目前没有自动 LRU 清理。数据库没有独立加密,本地记录的读取仍受站点 origin 控制。
|
|
163
|
+
|
|
164
|
+
## 开发验证
|
|
165
|
+
|
|
166
|
+
使用 Node 18+ 执行 `npm run test:sdk`;使用 `npx tsc --noEmit` 校验类型,`npm run build:sdk` 更新发行构建。存储测试使用 fake-indexeddb,覆盖失败记录恢复、去重、分页、账号隔离、编辑撤回删除、媒体失败、同步及存储降级;Socket.IO 模拟推送另覆盖批量重复推送、回调篡改隔离、数据库重开、历史请求竞态、原生及兼容快照、写入失败恢复。真实服务器协议仍需联调。
|
package/README.md
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
# Vanshi Chat SDK
|
|
2
2
|
|
|
3
|
-
浏览器侧 IM SDK,提供 socket
|
|
3
|
+
浏览器侧 IM SDK,提供 socket 连接、登录、会话、文本消息、媒体消息、历史消息、本地消息搜索、消息查询、用户在线状态、消息回执和事件监听能力。
|
|
4
4
|
|
|
5
|
-
当前版本:`vanshi-sdk@1.
|
|
5
|
+
当前版本:`vanshi-sdk@1.2.26`
|
|
6
6
|
|
|
7
7
|
## 安装
|
|
8
8
|
|
|
9
9
|
内网 npm:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npm install vanshi-sdk@1.
|
|
12
|
+
npm install vanshi-sdk@1.2.26 --registry=https://npm.whatimcloud.com/
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
默认 registry 已配置到内网时:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm install vanshi-sdk@1.
|
|
18
|
+
npm install vanshi-sdk@1.2.26
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## 引入方式
|
|
@@ -74,6 +74,28 @@ window.__VANSHI_SOCKET_URL = 'wss://your-socket-host'
|
|
|
74
74
|
window.__VANSHI_HTTP_URL = 'https://your-api-host/api/files/sdkUpload'
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
## 本地聊天记录
|
|
78
|
+
|
|
79
|
+
登录时可传入 `userId` 开启 IndexedDB 消息持久化:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
await VanshiSdk.login({ token: 'your_token', userId: '10001' })
|
|
83
|
+
const messages = await VanshiSdk.queryLocalMessages({ conversationID: 'group-1', conversationType: 2 })
|
|
84
|
+
const failed = await VanshiSdk.queryFailedMessages({ conversationID: 'group-1', conversationType: 2 })
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
收发、失败消息、历史查询和消息变更自动落库。登录、刷新页面和重连恢复后直接复用本地记录,SDK 不自动拉取历史;需要补齐历史时,由业务主动查询或调用同步接口。使用方式、同步一致性边界及降级行为见 [本地存储说明](./LOCAL_STORAGE.md)。
|
|
88
|
+
|
|
89
|
+
切换账号会取消旧会话的排队请求,旧回包不能覆盖新会话。开启存储时登录过程中的消息先缓冲,数据库就绪后入库。`syncLocalMessages()` 遇到发现会话或部分会话同步失败会抛出 `LocalSyncError`,包含 `results` 和 `failures`,业务需处理拒绝。
|
|
90
|
+
|
|
91
|
+
## 连接保活
|
|
92
|
+
|
|
93
|
+
SDK 收到 `push:call` 后,会在调用业务事件监听器前自动返回 ACK,包含 `success`、`receivedAt`、`event`、`callId`(推送包含 `callID` 时)和 `platform: 'web'`。ACK 表示推送已接收;业务层无需手动确认。格式错误的推送返回 `success: false`,没有 ACK 回调的旧服务端仍可正常使用。
|
|
94
|
+
|
|
95
|
+
SDK 建立连接后会立即发送一次主动心跳,之后每 `30` 秒发送一次 Engine.IO `ping`。单次心跳超过 `90` 秒未收到对应 `pong` 时,SDK 会关闭失效连接,并交由现有自动重连和登录恢复流程处理。
|
|
96
|
+
|
|
97
|
+
Socket.IO 服务端在握手中下发的原生心跳参数仍由服务端控制,SDK 主动心跳作为额外的连接保活与失效检测机制。
|
|
98
|
+
|
|
77
99
|
## 快速开始
|
|
78
100
|
|
|
79
101
|
```ts
|
|
@@ -90,7 +112,7 @@ async function bootstrap() {
|
|
|
90
112
|
throw new Error('SDK 登录失败')
|
|
91
113
|
}
|
|
92
114
|
|
|
93
|
-
const conversations = await VanshiSdk.
|
|
115
|
+
const conversations = await VanshiSdk.queryConversationList({
|
|
94
116
|
count: 20,
|
|
95
117
|
nextConversation: undefined,
|
|
96
118
|
})
|
|
@@ -166,12 +188,16 @@ if (file) {
|
|
|
166
188
|
| `type` | `string` | 消息类型 |
|
|
167
189
|
| `message` | `string` | 消息内容 |
|
|
168
190
|
| `sentStatus` | `number` | 本地发送状态,`0` 表示发送中 |
|
|
169
|
-
| `fileName` | `string` |
|
|
191
|
+
| `fileName` | `string` | 文件名主名称最长 20 个字符,文件后缀完整保留;上传接口也使用截取后的文件名 |
|
|
170
192
|
| `fileSize` | `string` | 文件大小 |
|
|
171
193
|
| `localPreviewUrl` | `string` | 浏览器本地预览地址 |
|
|
172
194
|
|
|
173
195
|
## 常用事件
|
|
174
196
|
|
|
197
|
+
`conversationChanged`、`receivePeerMessage`、`receiveGroupMessage` 的 ACK 均由 SDK 在业务回调前自动发送,无业务监听器时也会回执。业务代码只接收事件数据,不再接收或调用 ACK 函数。消息推送使用 `{ clientId, platform: 'web', msgid }`,会话变更使用 `{ clientId, platform: 'web', ackIds, userId }`;ACK 表示已接收推送,不代表消息已读。
|
|
198
|
+
|
|
199
|
+
登录可传 `userID` 为会话变更回执提供当前用户身份:`login({ token, userID: '10001' })`;此参数不发送到登录接口,也不会开启本地数据库。已有的 `userId` 参数仍用于开启本地数据库,未传 `userID` 时也作为 ACK 用户身份。
|
|
200
|
+
|
|
175
201
|
```ts
|
|
176
202
|
const handler = (payload: unknown) => {
|
|
177
203
|
console.log(payload)
|
|
@@ -179,7 +205,27 @@ const handler = (payload: unknown) => {
|
|
|
179
205
|
|
|
180
206
|
VanshiSdk.on('conversationChanged', handler)
|
|
181
207
|
VanshiSdk.on('receiveGroupMessage', handler)
|
|
208
|
+
VanshiSdk.on('receivePeerMessage', handler)
|
|
209
|
+
VanshiSdk.on('sendSingLing', handler)
|
|
182
210
|
VanshiSdk.on('messageReceiptChanged', handler)
|
|
211
|
+
VanshiSdk.on('userStatusUpdated', ({ userStatusList }) => {
|
|
212
|
+
console.log('订阅用户状态变化', userStatusList)
|
|
213
|
+
})
|
|
214
|
+
VanshiSdk.on('disconnect', reason => {
|
|
215
|
+
console.log('SDK 已断开', reason)
|
|
216
|
+
})
|
|
217
|
+
VanshiSdk.on('connect', () => {
|
|
218
|
+
console.log('SDK 已连接')
|
|
219
|
+
})
|
|
220
|
+
VanshiSdk.on('reconnectLoginSuccess', () => {
|
|
221
|
+
console.log('SDK 重连并恢复登录成功')
|
|
222
|
+
})
|
|
223
|
+
VanshiSdk.on('reconnectLoginFailed', error => {
|
|
224
|
+
console.error('SDK 重连后恢复登录失败', error)
|
|
225
|
+
})
|
|
226
|
+
VanshiSdk.on('connectionStateChanged', payload => {
|
|
227
|
+
console.log('SDK 连接状态变化', payload)
|
|
228
|
+
})
|
|
183
229
|
|
|
184
230
|
VanshiSdk.off('conversationChanged', handler)
|
|
185
231
|
```
|
|
@@ -190,15 +236,151 @@ VanshiSdk.off('conversationChanged', handler)
|
|
|
190
236
|
| --- | --- |
|
|
191
237
|
| `create()` | 创建 socket 连接 |
|
|
192
238
|
| `login({ token })` | 登录 SDK |
|
|
193
|
-
| `logout()` | 退出登录并清理本地 token |
|
|
239
|
+
| `logout()` | 退出登录并清理本地 token,默认不主动断开 socket |
|
|
194
240
|
| `isConnected()` | 判断 socket 是否连接 |
|
|
195
|
-
| `queryConversation(
|
|
241
|
+
| `queryConversation(conversationID, conversationType)` | 按 ID 和类型查询单个会话,返回 `{ conversation }` |
|
|
242
|
+
| `queryConversationList(config)` | 分页查询会话列表 |
|
|
196
243
|
| `sendMessage(params)` | 发送文本消息 |
|
|
197
244
|
| `sendMediaMessage(params, options?)` | 发送媒体消息,支持本地消息预回调 |
|
|
198
245
|
| `queryHistoryMessage(params)` | 查询历史消息 |
|
|
246
|
+
| `searchLocalMessages(conversationID, conversationType, config)` | 搜索本地缓存消息 |
|
|
247
|
+
| `queryMessages(messageIds, conversationID, conversationType)` | 按消息 ID 查询消息 |
|
|
248
|
+
| `queryUsersStatus(userIDs)` | 批量查询用户在线状态 |
|
|
249
|
+
| `subscribeUsersStatus(userIDs, config)` | 订阅用户在线状态变化,`subscriptionDuration` 单位为秒 |
|
|
250
|
+
| `unsubscribeUsersStatus(userIDs)` | 解除用户在线状态订阅 |
|
|
199
251
|
| `sendMessageReceiptsRead(...)` | 发送已读回执 |
|
|
200
252
|
| `queryMessageReceiptsInfo(...)` | 查询消息回执 |
|
|
201
253
|
|
|
254
|
+
## 音视频通话
|
|
255
|
+
|
|
256
|
+
SDK 已内置 LiveKit Web 媒体适配器。IM 信令仍由 Vanshi socket 负责,LiveKit 只负责浏览器音视频传输。
|
|
257
|
+
|
|
258
|
+
通话加入、设备采集和屏幕共享发布失败会释放已经取得的轨道;退出或切换账号会取消尚未完成的媒体操作。重复设备启停按顺序处理,避免重复采集;`callLeaveMedia` 等待房间断开完成。
|
|
259
|
+
|
|
260
|
+
### 1v1 音视频
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
import VanshiSdk, { ZIMCallConversationType, ZIMCallType } from 'vanshi-sdk'
|
|
264
|
+
|
|
265
|
+
VanshiSdk.on('callInvitationReceived', async (payload) => {
|
|
266
|
+
// 业务 UI 弹出接听面板,然后由用户触发 accept。
|
|
267
|
+
console.log('incoming call', payload)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
const inviteResult = await VanshiSdk.callInvite({
|
|
271
|
+
conversationID: 'user_b',
|
|
272
|
+
conversationType: ZIMCallConversationType.Peer,
|
|
273
|
+
invitees: ['user_b'],
|
|
274
|
+
callType: ZIMCallType.Video,
|
|
275
|
+
mode: 'one_on_one',
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
await VanshiSdk.callJoinMedia({
|
|
279
|
+
callID: inviteResult.callID,
|
|
280
|
+
liveKit: inviteResult.liveKit,
|
|
281
|
+
audio: true,
|
|
282
|
+
video: true,
|
|
283
|
+
})
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
被叫接听:
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
const accepted = await VanshiSdk.callAccept({ callID })
|
|
290
|
+
|
|
291
|
+
await VanshiSdk.callJoinMedia({
|
|
292
|
+
callID,
|
|
293
|
+
liveKit: accepted.liveKit,
|
|
294
|
+
audio: true,
|
|
295
|
+
video: true,
|
|
296
|
+
})
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### 多人音视频
|
|
300
|
+
|
|
301
|
+
多人通话和 1v1 使用同一套 LiveKit room。服务端为同一个 `callID` 生成同一个 `roomName`,每个成员单独签发自己的 LiveKit token。
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
const call = await VanshiSdk.callInvite({
|
|
305
|
+
conversationID: 'group_1',
|
|
306
|
+
conversationType: ZIMCallConversationType.Group,
|
|
307
|
+
invitees: ['user_a', 'user_b', 'user_c'],
|
|
308
|
+
callType: ZIMCallType.Video,
|
|
309
|
+
mode: 'group',
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
await VanshiSdk.callJoinMedia({
|
|
313
|
+
callID: call.callID,
|
|
314
|
+
liveKit: call.liveKit,
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
await VanshiSdk.callInviteMembers({
|
|
318
|
+
callID: call.callID,
|
|
319
|
+
invitees: ['user_d'],
|
|
320
|
+
})
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### 渲染远端音视频
|
|
324
|
+
|
|
325
|
+
业务层监听 LiveKit 轨道事件,自行把 `track` attach 到 `<video>` 或 `<audio>`。
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
VanshiSdk.on('callMediaTrackSubscribed', ({ track, participantID }) => {
|
|
329
|
+
const element = document.createElement(track.kind === 'video' ? 'video' : 'audio')
|
|
330
|
+
element.autoplay = true
|
|
331
|
+
element.playsInline = true
|
|
332
|
+
element.dataset.participantId = participantID
|
|
333
|
+
track.attach(element)
|
|
334
|
+
document.body.appendChild(element)
|
|
335
|
+
})
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
常用控制:
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
await VanshiSdk.callSetMicrophoneEnabled(false)
|
|
342
|
+
await VanshiSdk.callSetCameraEnabled(true)
|
|
343
|
+
await VanshiSdk.callStartScreenShare()
|
|
344
|
+
await VanshiSdk.callStopScreenShare()
|
|
345
|
+
await VanshiSdk.callHangup({ callID })
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
获取当前会话是否存在通话:
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
const result = await VanshiSdk.callHasOngoingCall({ groupId: 'group-1' })
|
|
352
|
+
// result: { hasOngoingCall: boolean, roomId: string, roomNumber: number, roomFlowType: number }
|
|
353
|
+
// roomFlowType: 1=音频,2=视频;hasOngoingCall=true 时 roomId 有值。
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
入参及返回数据字段与 `im-chat-web` 的 `/v1/im/chat/group/hasOngoingCall` 接口一致,SDK 返回 ACK 的 `data`,业务错误按通话 API 的统一方式抛出。
|
|
357
|
+
|
|
358
|
+
### 后端协议
|
|
359
|
+
|
|
360
|
+
通话信令使用 Socket.IO 事件 ACK,不经过通用的 `api:request` / HTTP-style path。SDK 会按下面的事件名执行:
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
socket.emit('call/create', payload, (response) => {})
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
所有 ACK 响应格式为 `{ code, message, requestId?, data }`。业务失败会由 SDK 抛出 `SdkRuntimeErrorObject`。
|
|
367
|
+
|
|
368
|
+
| Socket.IO ACK event | 说明 |
|
|
369
|
+
| --- | --- |
|
|
370
|
+
| `call/create` | 创建 1v1 或多人通话,返回 `callID`、`roomName`、可选 `liveKit` token |
|
|
371
|
+
| `call/inviteMembers` | 多人通话追加邀请成员 |
|
|
372
|
+
| `call/accept` | 接听通话,返回可选 `liveKit` token |
|
|
373
|
+
| `call/reject` | 拒绝通话 |
|
|
374
|
+
| `call/cancel` | 主叫取消未接通通话 |
|
|
375
|
+
| `call/hangup` | 挂断或离开通话 |
|
|
376
|
+
| `call/members` | 查询通话成员 |
|
|
377
|
+
| `call/token` | 为当前登录用户签发 LiveKit token |
|
|
378
|
+
| `call/hasOngoingCall` | 获取当前会话是否存在通话,入参 `{ groupId }` |
|
|
379
|
+
|
|
380
|
+
`call/create` 和其他事件的具体 payload 以音视频接口对接文档为准。为兼容旧版 SDK 调用,`callInvite` 传入旧的 `ZIMConversationType.Group`(值为 `2`)时会自动转换为通话协议的群聊值 `1`。
|
|
381
|
+
|
|
382
|
+
后端使用 `livekit-server-sdk` 签发 token。LiveKit `apiSecret` 只能保存在服务端,不能下发到 Web。
|
|
383
|
+
|
|
202
384
|
## 详细文档
|
|
203
385
|
|
|
204
386
|
- [VANSHI_SDK.md](./VANSHI_SDK.md):客户接入完整文档
|