magic-chat-im 2.9.2
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/README.md +928 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,928 @@
|
|
|
1
|
+
# IM SDK
|
|
2
|
+
|
|
3
|
+
基于 WebSocket 的即时通讯 SDK,提供完整的连接管理、消息收发、智能重连和心跳检测等功能。
|
|
4
|
+
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
## 特性
|
|
9
|
+
|
|
10
|
+
### 核心连接能力
|
|
11
|
+
✅ **智能重连机制** - 指数退避算法 + 随机抖动,避免惊群效应
|
|
12
|
+
✅ **连接状态管理** - 完整的 WebSocket 状态跟踪和管理
|
|
13
|
+
✅ **心跳保活检测** - 自动心跳机制,及时发现连接异常
|
|
14
|
+
✅ **网络状态监听** - 支持网络变化和 Tab 切换自动重连
|
|
15
|
+
✅ **移动端优化** - 针对移动设备的连接稳定性和性能优化
|
|
16
|
+
|
|
17
|
+
### 消息处理能力
|
|
18
|
+
✅ **可靠消息传输** - 断线期间消息不丢失,重连后自动发送
|
|
19
|
+
✅ **消息去重机制** - 基于 messageKey 的智能去重功能
|
|
20
|
+
✅ **消息队列管理** - 支持消息缓存和批量处理
|
|
21
|
+
✅ **ACK确认机制** - 确保消息可靠到达
|
|
22
|
+
|
|
23
|
+
### 监控与诊断
|
|
24
|
+
✅ **连接统计信息** - 详细的连接、重连、消息统计
|
|
25
|
+
✅ **性能指标监控** - 连接成功率、消息丢失率等关键指标
|
|
26
|
+
✅ **日志系统** - 多级别日志记录和自定义日志处理器
|
|
27
|
+
✅ **错误追踪** - 完善的错误处理和异常上报机制
|
|
28
|
+
|
|
29
|
+
### 开发体验
|
|
30
|
+
✅ **TypeScript支持** - 完整的类型定义,优秀的开发体验
|
|
31
|
+
✅ **事件驱动模型** - 灵活的事件系统,支持自定义监听
|
|
32
|
+
✅ **配置参数化** - 支持心跳、重连、日志等参数自定义
|
|
33
|
+
✅ **API设计优雅** - 简洁直观的 API 设计,易于集成使用
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 安装
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install @alife/magic-chat-im
|
|
41
|
+
# or
|
|
42
|
+
yarn add @alife/magic-chat-im
|
|
43
|
+
# or
|
|
44
|
+
pnpm add @alife/magic-chat-im
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 快速开始
|
|
50
|
+
|
|
51
|
+
### 基础用法
|
|
52
|
+
|
|
53
|
+
``typescript
|
|
54
|
+
import IM, { EVT_TYPE } from '@alife/magic-chat-im';
|
|
55
|
+
|
|
56
|
+
// 创建实例
|
|
57
|
+
const im = new IM({
|
|
58
|
+
url: 'wss://example.com/ws',
|
|
59
|
+
appKey: 'your-app-key',
|
|
60
|
+
getToken: async () => {
|
|
61
|
+
// 动态获取 token
|
|
62
|
+
return await fetchToken();
|
|
63
|
+
},
|
|
64
|
+
commonParams: {
|
|
65
|
+
appId: 'your-app-id',
|
|
66
|
+
userId: 'user-123',
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// 监听连接成功
|
|
71
|
+
im.on(EVT_TYPE.CONNECT, () => {
|
|
72
|
+
console.log('连接成功');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// 监听消息
|
|
76
|
+
im.on(EVT_TYPE.MESSAGE, (msg) => {
|
|
77
|
+
console.log('收到消息:', msg);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// 发送消息
|
|
81
|
+
im.sendMessage({
|
|
82
|
+
msgType: 'text',
|
|
83
|
+
content: 'Hello World',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// 组件卸载时销毁
|
|
87
|
+
componentWillUnmount() {
|
|
88
|
+
im.destroy();
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 带 TypeScript 类型
|
|
93
|
+
|
|
94
|
+
``typescript
|
|
95
|
+
import IM, {
|
|
96
|
+
EVT_TYPE,
|
|
97
|
+
type MessageBody,
|
|
98
|
+
type ReconnectEventData,
|
|
99
|
+
type ErrorEventData,
|
|
100
|
+
} from '@alife/magic-chat-im';
|
|
101
|
+
|
|
102
|
+
const im = new IM({
|
|
103
|
+
url: 'wss://example.com/ws',
|
|
104
|
+
appKey: 'your-app-key',
|
|
105
|
+
getToken: async () => 'your-token',
|
|
106
|
+
|
|
107
|
+
// 带类型的回调
|
|
108
|
+
messageCallback: (msg: MessageBody) => {
|
|
109
|
+
console.log('消息类型:', msg.msgType);
|
|
110
|
+
console.log('消息内容:', msg);
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
reconnectCallback: (data: ReconnectEventData) => {
|
|
114
|
+
console.log(`重连中... ${data.attempt}/${data.maxRetries}`);
|
|
115
|
+
console.log(`下次重连延迟: ${data.nextRetryDelay}ms`);
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
errorCallback: (err: ErrorEventData) => {
|
|
119
|
+
console.error('错误:', err.code, err.msg);
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
### 在线demo
|
|
127
|
+
|
|
128
|
+
[demo](https://pre-fe.gts.work/magic-chat/ims/demos)
|
|
129
|
+
|
|
130
|
+
## API 文档
|
|
131
|
+
|
|
132
|
+
### 构造函数
|
|
133
|
+
|
|
134
|
+
#### `new IM(options: Options)`
|
|
135
|
+
|
|
136
|
+
创建 IM 实例。
|
|
137
|
+
|
|
138
|
+
**参数:**
|
|
139
|
+
|
|
140
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
141
|
+
|------|------|------|------|
|
|
142
|
+
| `url` | `string` | 是 | WebSocket 服务器地址 |
|
|
143
|
+
| `appKey` | `string` | 是 | 应用标识 |
|
|
144
|
+
| `token` | `string` | 否 | 访问令牌(二选一) |
|
|
145
|
+
| `getToken` | `() => Promise<string>` | 否 | 动态获取 token 的函数(二选一) |
|
|
146
|
+
| `commonParams` | `CommonParams` | 否 | 全局参数(appId, userId) |
|
|
147
|
+
| `reconnectMaxRetry` | `number` | 否 | 10 | 最大重连次数(推荐 10-15) |
|
|
148
|
+
| `supportNetworkRestoredReconnect` | `boolean` | 否 | false | 是否启用网络状态监听 |
|
|
149
|
+
| `loggerConfig` | `Partial<LoggerConfig>` | 否 | 见说明 | 日志配置(默认根据环境自动配置) |
|
|
150
|
+
| `connectCallback` | `() => void` | 否 | 连接成功回调 |
|
|
151
|
+
| `messageCallback` | `(msg: MessageBody) => void` | 否 | 消息接收回调 |
|
|
152
|
+
| `ackCallback` | `(msg: MessageData) => void` | 否 | ACK 消息回调 |
|
|
153
|
+
| `reconnectCallback` | `(data: ReconnectEventData) => void` | 否 | 重连事件回调 |
|
|
154
|
+
| `errorCallback` | `(err: ErrorEventData) => void` | 否 | 错误回调 |
|
|
155
|
+
| `closeCallback` | `(err: CloseEventData) => void` | 否 | 连接关闭回调 |
|
|
156
|
+
| `connectionTimeout` | `number` | 否 | 15000 连接超时时间(毫秒),建议10-30秒 |
|
|
157
|
+
|
|
158
|
+
**示例:**
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { LogLevel } from '@alife/magic-chat-im';
|
|
162
|
+
|
|
163
|
+
const im = new IM({
|
|
164
|
+
url: 'wss://example.com/ws',
|
|
165
|
+
appKey: 'app-123',
|
|
166
|
+
getToken: async () => {
|
|
167
|
+
const res = await fetch('/api/token');
|
|
168
|
+
return res.json().token;
|
|
169
|
+
},
|
|
170
|
+
commonParams: {
|
|
171
|
+
appId: 'app-123',
|
|
172
|
+
userId: 'user-456',
|
|
173
|
+
},
|
|
174
|
+
reconnectMaxRetry: 10, // 推荐 10-15 次 (约 3-5 分钟)
|
|
175
|
+
supportNetworkRestoredReconnect: true,
|
|
176
|
+
|
|
177
|
+
// 日志配置(可选)
|
|
178
|
+
loggerConfig: {
|
|
179
|
+
level: LogLevel.INFO, // 日志级别
|
|
180
|
+
enabled: true, // 是否启用
|
|
181
|
+
timestamp: true, // 显示时间戳
|
|
182
|
+
prefix: '[我的应用]', // 自定义前缀
|
|
183
|
+
handler: (level, message, ...args) => {
|
|
184
|
+
// 自定义处理器(如上报到服务器)
|
|
185
|
+
if (level === LogLevel.ERROR) {
|
|
186
|
+
reportToServer({ level, message, args });
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
messageCallback: (msg) => {
|
|
192
|
+
console.log('收到消息:', msg);
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
### 实例方法
|
|
200
|
+
|
|
201
|
+
#### `connect()`
|
|
202
|
+
|
|
203
|
+
建立连接。
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
im.connect();
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### `sendMessage(msgBody: Partial<MessageBody>)`
|
|
210
|
+
|
|
211
|
+
发送消息。
|
|
212
|
+
|
|
213
|
+
**参数:**
|
|
214
|
+
- `msgBody`: 消息体对象
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
im.sendMessage({
|
|
218
|
+
msgType: 'text',
|
|
219
|
+
content: 'Hello',
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
#### `ping()`
|
|
224
|
+
|
|
225
|
+
发送心跳。
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
im.ping();
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
#### `close()`
|
|
232
|
+
|
|
233
|
+
主动关闭连接(不会触发重连)。
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
im.close();
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### `reconnect(code: RECONNECT_RESON, error?: string | Event)`
|
|
240
|
+
|
|
241
|
+
手动触发重连。
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
im.reconnect(RECONNECT_RESON.POSTMESSAGE);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
#### `manualReconnect()`
|
|
248
|
+
|
|
249
|
+
手动重连(用于达到最大重连次数后)。
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
im.manualReconnect();
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### `destroy()`
|
|
256
|
+
|
|
257
|
+
销毁实例,清理所有资源。
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
im.destroy();
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
#### `readyState(): number`
|
|
264
|
+
|
|
265
|
+
获取 WebSocket 连接状态。
|
|
266
|
+
|
|
267
|
+
**返回值:**
|
|
268
|
+
- `0` - CONNECTING:正在连接
|
|
269
|
+
- `1` - OPEN:已连接
|
|
270
|
+
- `2` - CLOSING:正在关闭
|
|
271
|
+
- `3` - CLOSED:已关闭
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
const state = im.readyState();
|
|
275
|
+
if (state === 1) {
|
|
276
|
+
console.log('已连接');
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
#### `setCommonParams(param: CommonParams)`
|
|
281
|
+
|
|
282
|
+
设置全局参数。
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
im.setCommonParams({
|
|
286
|
+
appId: 'new-app-id',
|
|
287
|
+
userId: 'new-user-id',
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
#### `setUserClosed(closed: boolean)`
|
|
292
|
+
|
|
293
|
+
设置用户关闭状态(用于测试)。
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
// 模拟系统错误,允许重连
|
|
297
|
+
im.setUserClosed(false);
|
|
298
|
+
|
|
299
|
+
// 模拟用户主动关闭,阻止重连
|
|
300
|
+
im.setUserClosed(true);
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
#### `getConnectionStats()`
|
|
304
|
+
|
|
305
|
+
获取连接统计信息。
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
const stats = im.getConnectionStats();
|
|
309
|
+
console.log('连接统计:', stats);
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**返回值:**
|
|
313
|
+
```typescript
|
|
314
|
+
{
|
|
315
|
+
totalConnections: number; // 总连接次数
|
|
316
|
+
totalReconnections: number; // 总重连次数
|
|
317
|
+
totalMessagesSent: number; // 总发送消息数
|
|
318
|
+
totalMessagesReceived: number; // 总接收消息数
|
|
319
|
+
totalHeartbeatTimeouts: number; // 总心跳超时次数
|
|
320
|
+
firstConnectionTime: number; // 首次连接时间戳
|
|
321
|
+
lastConnectTime: number; // 最后连接时间戳
|
|
322
|
+
lastDisconnectTime: number; // 最后断开时间戳
|
|
323
|
+
totalOnlineTime: number; // 总在线时长(毫秒)
|
|
324
|
+
currentOnlineTime: number; // 当前在线时长(毫秒)
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
#### `getPerformanceMetrics()`
|
|
329
|
+
|
|
330
|
+
获取性能指标。
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
const metrics = im.getPerformanceMetrics();
|
|
334
|
+
console.log('性能指标:', metrics);
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**返回值:**
|
|
338
|
+
```typescript
|
|
339
|
+
{
|
|
340
|
+
connectionSuccessRate: number; // 连接成功率
|
|
341
|
+
messageLossRate: number; // 消息丢失率
|
|
342
|
+
averageReconnectionTime: number; // 平均重连时间
|
|
343
|
+
heartbeatTimeoutRate: number; // 心跳超时率
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
#### `resetConnectionStats()`
|
|
348
|
+
|
|
349
|
+
重置连接统计信息。
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
im.resetConnectionStats();
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### 监控方法
|
|
356
|
+
|
|
357
|
+
#### `getUserClosed(): boolean`
|
|
358
|
+
|
|
359
|
+
获取用户关闭状态。
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
const isUserClosed = im.getUserClosed();
|
|
363
|
+
console.log('是否用户主动关闭:', isUserClosed);
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
#### `getReconnectState(): ReconnectState | undefined`
|
|
367
|
+
|
|
368
|
+
获取当前重连状态。
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
import { ReconnectState } from '@alife/magic-chat-im';
|
|
372
|
+
|
|
373
|
+
const state = im.getReconnectState();
|
|
374
|
+
if (state === ReconnectState.CONNECTING) {
|
|
375
|
+
console.log('正在重连中...');
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**状态值:**
|
|
380
|
+
- `ReconnectState.IDLE` - 空闲,没有重连
|
|
381
|
+
- `ReconnectState.SCHEDULED` - 已调度,等待定时器触发
|
|
382
|
+
- `ReconnectState.CONNECTING` - 正在连接中
|
|
383
|
+
|
|
384
|
+
#### `getReconnectCounter(): number`
|
|
385
|
+
|
|
386
|
+
获取当前重连次数。
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
const count = im.getReconnectCounter();
|
|
390
|
+
const max = im.getReconnectMaxRetry();
|
|
391
|
+
console.log(`重连进度: ${count}/${max}`);
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
#### `getReconnectMaxRetry(): number`
|
|
395
|
+
|
|
396
|
+
获取最大重连次数。
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
const maxRetry = im.getReconnectMaxRetry();
|
|
400
|
+
console.log('最大重连次数:', maxRetry);
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
#### `getQueueSize(): number`
|
|
404
|
+
|
|
405
|
+
获取待发送消息队列大小。
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
const queueSize = im.getQueueSize();
|
|
409
|
+
if (queueSize > 50) {
|
|
410
|
+
console.warn('消息队列已积压:', queueSize);
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
### 事件监听
|
|
417
|
+
|
|
418
|
+
#### `on(event: EVT_TYPE, handler: Function)`
|
|
419
|
+
|
|
420
|
+
监听事件。
|
|
421
|
+
|
|
422
|
+
**事件类型:**
|
|
423
|
+
|
|
424
|
+
| 事件 | 说明 | 回调参数 |
|
|
425
|
+
|------|------|----------|
|
|
426
|
+
| `EVT_TYPE.OPEN` | WebSocket 连接打开 | 无 |
|
|
427
|
+
| `EVT_TYPE.CONNECT` | 连接建立成功 | 无 |
|
|
428
|
+
| `EVT_TYPE.MESSAGE` | 收到业务消息 | `MessageBody` |
|
|
429
|
+
| `EVT_TYPE.ACK` | 收到 ACK 消息 | `MessageData` |
|
|
430
|
+
| `EVT_TYPE.ERROR` | 发生错误 | `ErrorEventData` |
|
|
431
|
+
| `EVT_TYPE.CLOSE` | 连接关闭 | `CloseEventData` |
|
|
432
|
+
| `EVT_TYPE.RECONNECT` | 重连事件 | `ReconnectEventData` |
|
|
433
|
+
|
|
434
|
+
**示例:**
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
// 监听连接
|
|
438
|
+
im.on(EVT_TYPE.CONNECT, () => {
|
|
439
|
+
console.log('已连接');
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// 监听消息
|
|
443
|
+
im.on(EVT_TYPE.MESSAGE, (msg: MessageBody) => {
|
|
444
|
+
console.log('收到消息:', msg);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// 监听重连
|
|
448
|
+
im.on(EVT_TYPE.RECONNECT, (data: ReconnectEventData) => {
|
|
449
|
+
console.log(`重连中 ${data.attempt}/${data.maxRetries}`);
|
|
450
|
+
console.log(`下次延迟 ${data.nextRetryDelay}ms`);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// 监听错误
|
|
454
|
+
im.on(EVT_TYPE.ERROR, (err: ErrorEventData) => {
|
|
455
|
+
console.error('错误:', err.code, err.msg);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// 监听关闭
|
|
459
|
+
im.on(EVT_TYPE.CLOSE, (data: CloseEventData) => {
|
|
460
|
+
if (data.code === EXCEPTION_ERROR.NEED_RECONNECT_MANUAL) {
|
|
461
|
+
// 提示用户手动重连
|
|
462
|
+
showReconnectButton();
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## 类型定义
|
|
470
|
+
|
|
471
|
+
### MessageBody
|
|
472
|
+
|
|
473
|
+
消息体类型。
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
interface MessageBody {
|
|
477
|
+
version?: string;
|
|
478
|
+
msgType?: string;
|
|
479
|
+
msgDetail?: any;
|
|
480
|
+
[key: string]: any;
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### MessageData
|
|
485
|
+
|
|
486
|
+
完整消息数据。
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
interface MessageData {
|
|
490
|
+
name: string;
|
|
491
|
+
args?: any[];
|
|
492
|
+
messageKey: string;
|
|
493
|
+
msgType: RECEIVED_MSG_TYPE | SEND_MSG_TYPE;
|
|
494
|
+
msgBody: MessageBody;
|
|
495
|
+
appId?: string;
|
|
496
|
+
userId?: string;
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### ReconnectEventData
|
|
501
|
+
|
|
502
|
+
重连事件数据。
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
interface ReconnectEventData {
|
|
506
|
+
code: RECONNECT_RESON;
|
|
507
|
+
err?: any;
|
|
508
|
+
attempt?: number;
|
|
509
|
+
maxRetries?: number;
|
|
510
|
+
nextRetryDelay?: number;
|
|
511
|
+
isReconnecting?: boolean;
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### ErrorEventData
|
|
516
|
+
|
|
517
|
+
错误事件数据。
|
|
518
|
+
|
|
519
|
+
```typescript
|
|
520
|
+
interface ErrorEventData {
|
|
521
|
+
code: EXCEPTION_ERROR;
|
|
522
|
+
err?: any;
|
|
523
|
+
msg?: string;
|
|
524
|
+
reason?: string;
|
|
525
|
+
maxRetry?: number;
|
|
526
|
+
[key: string]: any;
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### CloseEventData
|
|
531
|
+
|
|
532
|
+
关闭事件数据。
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
interface CloseEventData {
|
|
536
|
+
code: EXCEPTION_ERROR;
|
|
537
|
+
err?: any;
|
|
538
|
+
reason?: string;
|
|
539
|
+
[key: string]: any;
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
## 高级用法
|
|
546
|
+
|
|
547
|
+
### 处理最大重连次数
|
|
548
|
+
|
|
549
|
+
当达到最大重连次数时,会触发 `NEED_RECONNECT_MANUAL` 错误,此时需要用户手动重连。
|
|
550
|
+
|
|
551
|
+
``typescript
|
|
552
|
+
import { EVT_TYPE, EXCEPTION_ERROR } from '@alife/magic-chat-im';
|
|
553
|
+
|
|
554
|
+
im.on(EVT_TYPE.CLOSE, (data) => {
|
|
555
|
+
if (data.code === EXCEPTION_ERROR.NEED_RECONNECT_MANUAL) {
|
|
556
|
+
// 显示重连按钮
|
|
557
|
+
showDialog({
|
|
558
|
+
title: '连接已断开',
|
|
559
|
+
message: '请点击重连按钮',
|
|
560
|
+
confirmText: '重连',
|
|
561
|
+
onConfirm: () => {
|
|
562
|
+
im.manualReconnect();
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### 监听网络状态变化
|
|
570
|
+
|
|
571
|
+
启用网络状态监听后,SDK 会自动处理网络变化 (浏览器`tab` 切换、网络断开、网络恢复):
|
|
572
|
+
|
|
573
|
+
``typescript
|
|
574
|
+
const im = new IM({
|
|
575
|
+
// ... 其他配置
|
|
576
|
+
supportNetworkRestoredReconnect: true,
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// ⚠️ 重要:必须在销毁时调用 destroy, destroy 里面会调用close 关闭连接,清理定时器等
|
|
580
|
+
componentWillUnmount() {
|
|
581
|
+
im.destroy(); // 移除网络事件监听器
|
|
582
|
+
}
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### 消息队列
|
|
586
|
+
|
|
587
|
+
断线期间的消息会自动加入队列,重连后批量发送:
|
|
588
|
+
|
|
589
|
+
```
|
|
590
|
+
// 即使断线,消息也不会丢失
|
|
591
|
+
im.sendMessage({ content: 'Message 1' });
|
|
592
|
+
im.sendMessage({ content: 'Message 2' });
|
|
593
|
+
im.sendMessage({ content: 'Message 3' });
|
|
594
|
+
|
|
595
|
+
// 重连后,这些消息会按顺序发送
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### 自定义重连次数
|
|
599
|
+
|
|
600
|
+
```
|
|
601
|
+
const im = new IM({
|
|
602
|
+
// ... 其他配置
|
|
603
|
+
reconnectMaxRetry: 15, // 最多重连 15 次 (约 5.5 分钟)
|
|
604
|
+
// 不推荐超过 20 次,会导致过长的重连时间
|
|
605
|
+
});
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### 日志配置
|
|
609
|
+
|
|
610
|
+
#### 基本使用(推荐)
|
|
611
|
+
|
|
612
|
+
```
|
|
613
|
+
import IM, { LogLevel } from '@alife/magic-chat-im';
|
|
614
|
+
|
|
615
|
+
// 方式 1:使用默认配置(根据环境自动调整)
|
|
616
|
+
const im = new IM({
|
|
617
|
+
url: 'wss://example.com/ws',
|
|
618
|
+
appKey: 'your-app-key',
|
|
619
|
+
getToken: async () => 'your-token',
|
|
620
|
+
// loggerConfig 可选,不传则使用默认配置
|
|
621
|
+
// 开发环境:{ level: DEBUG, enabled: true }
|
|
622
|
+
// 生产环境:{ level: WARN, enabled: false }
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
// 方式 2:自定义日志配置
|
|
626
|
+
const im = new IM({
|
|
627
|
+
url: 'wss://example.com/ws',
|
|
628
|
+
appKey: 'your-app-key',
|
|
629
|
+
getToken: async () => 'your-token',
|
|
630
|
+
|
|
631
|
+
loggerConfig: {
|
|
632
|
+
level: LogLevel.INFO, // 日志级别:DEBUG/INFO/WARN/ERROR/NONE
|
|
633
|
+
enabled: true, // 是否启用日志
|
|
634
|
+
timestamp: true, // 是否显示时间戳
|
|
635
|
+
prefix: '[我的应用]', // 自定义日志前缀
|
|
636
|
+
},
|
|
637
|
+
});
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
#### 集成监控系统(Sentry)
|
|
641
|
+
|
|
642
|
+
```
|
|
643
|
+
import IM, { LogLevel } from '@alife/magic-chat-im';
|
|
644
|
+
import * as Sentry from '@sentry/browser';
|
|
645
|
+
|
|
646
|
+
const im = new IM({
|
|
647
|
+
url: 'wss://example.com/ws',
|
|
648
|
+
appKey: 'your-app-key',
|
|
649
|
+
getToken: async () => 'your-token',
|
|
650
|
+
|
|
651
|
+
loggerConfig: {
|
|
652
|
+
level: LogLevel.DEBUG,
|
|
653
|
+
enabled: true,
|
|
654
|
+
handler: (level, message, ...args) => {
|
|
655
|
+
// 错误日志上报到 Sentry
|
|
656
|
+
if (level === LogLevel.ERROR) {
|
|
657
|
+
Sentry.captureMessage(message, {
|
|
658
|
+
level: 'error',
|
|
659
|
+
extra: { args },
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
},
|
|
664
|
+
});
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
#### 根据环境配置
|
|
668
|
+
|
|
669
|
+
```
|
|
670
|
+
import IM, { LogLevel } from '@alife/magic-chat-im';
|
|
671
|
+
|
|
672
|
+
const getLoggerConfig = () => {
|
|
673
|
+
switch (process.env.NODE_ENV) {
|
|
674
|
+
case 'development':
|
|
675
|
+
return { level: LogLevel.DEBUG, enabled: true };
|
|
676
|
+
case 'staging':
|
|
677
|
+
return { level: LogLevel.INFO, enabled: true };
|
|
678
|
+
case 'production':
|
|
679
|
+
return { level: LogLevel.WARN, enabled: false };
|
|
680
|
+
default:
|
|
681
|
+
return { level: LogLevel.ERROR, enabled: false };
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
const im = new IM({
|
|
686
|
+
url: 'wss://example.com/ws',
|
|
687
|
+
appKey: 'your-app-key',
|
|
688
|
+
getToken: async () => 'your-token',
|
|
689
|
+
loggerConfig: getLoggerConfig(),
|
|
690
|
+
});
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
**日志级别说明:**
|
|
694
|
+
|
|
695
|
+
| 级别 | 说明 | 适用场景 |
|
|
696
|
+
|------|------|----------|
|
|
697
|
+
| `LogLevel.DEBUG` | 调试信息(最详细) | 开发环境 |
|
|
698
|
+
| `LogLevel.INFO` | 一般信息 | 测试环境 |
|
|
699
|
+
| `LogLevel.WARN` | 警告信息 | 生产环境 |
|
|
700
|
+
| `LogLevel.ERROR` | 错误信息 | 所有环境 |
|
|
701
|
+
| `LogLevel.NONE` | 不输出日志 | 特殊场景 |
|
|
702
|
+
|
|
703
|
+
**日志输出示例:**
|
|
704
|
+
|
|
705
|
+
```
|
|
706
|
+
[IM SDK] [14:23:45.123] [INFO] Network is back online. Attempting to reconnect...
|
|
707
|
+
[IM SDK] [14:23:45.456] [WARN] ActionQueue is full, dropping oldest action
|
|
708
|
+
[IM SDK] [14:23:45.678] [ERROR] Heartbeat timeout, initiating reconnect
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
**默认配置:**
|
|
712
|
+
|
|
713
|
+
当不传 `loggerConfig` 时,使用以下默认配置:
|
|
714
|
+
|
|
715
|
+
```typescript
|
|
716
|
+
// 开发环境 (process.env.NODE_ENV !== 'production')
|
|
717
|
+
{
|
|
718
|
+
level: LogLevel.DEBUG,
|
|
719
|
+
enabled: true,
|
|
720
|
+
prefix: '[IM SDK]',
|
|
721
|
+
timestamp: true,
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// 生产环境 (process.env.NODE_ENV === 'production')
|
|
725
|
+
{
|
|
726
|
+
level: LogLevel.WARN,
|
|
727
|
+
enabled: false, // 禁用 INFO/DEBUG,ERROR 仍会输出
|
|
728
|
+
prefix: '[IM SDK]',
|
|
729
|
+
timestamp: true,
|
|
730
|
+
}
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
### React 集成示例
|
|
734
|
+
|
|
735
|
+
```
|
|
736
|
+
import { useEffect, useRef } from 'react';
|
|
737
|
+
import IM, { EVT_TYPE } from '@alife/magic-chat-im';
|
|
738
|
+
|
|
739
|
+
function Chat() {
|
|
740
|
+
const imRef = useRef<IM | null>(null);
|
|
741
|
+
|
|
742
|
+
useEffect(() => {
|
|
743
|
+
// 创建 IM 实例
|
|
744
|
+
const im = new IM({
|
|
745
|
+
url: 'wss://example.com/ws',
|
|
746
|
+
appKey: 'your-app-key',
|
|
747
|
+
getToken: async () => 'your-token',
|
|
748
|
+
supportNetworkRestoredReconnect: true,
|
|
749
|
+
messageCallback: (msg) => {
|
|
750
|
+
// 处理消息
|
|
751
|
+
handleMessage(msg);
|
|
752
|
+
},
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
imRef.current = im;
|
|
756
|
+
|
|
757
|
+
// 组件卸载时销毁
|
|
758
|
+
return () => {
|
|
759
|
+
im.destroy();
|
|
760
|
+
};
|
|
761
|
+
}, []);
|
|
762
|
+
|
|
763
|
+
const sendMessage = (content: string) => {
|
|
764
|
+
imRef.current?.sendMessage({
|
|
765
|
+
msgType: 'text',
|
|
766
|
+
content,
|
|
767
|
+
});
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
return (
|
|
771
|
+
<div>
|
|
772
|
+
<button onClick={() => sendMessage('Hello')}>
|
|
773
|
+
发送消息
|
|
774
|
+
</button>
|
|
775
|
+
</div>
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
### Vue 集成示例
|
|
781
|
+
|
|
782
|
+
```
|
|
783
|
+
import { onMounted, onUnmounted, ref } from 'vue';
|
|
784
|
+
import IM, { EVT_TYPE } from '@alife/magic-chat-im';
|
|
785
|
+
|
|
786
|
+
export default {
|
|
787
|
+
setup() {
|
|
788
|
+
const im = ref<IM | null>(null);
|
|
789
|
+
|
|
790
|
+
onMounted(() => {
|
|
791
|
+
im.value = new IM({
|
|
792
|
+
url: 'wss://example.com/ws',
|
|
793
|
+
appKey: 'your-app-key',
|
|
794
|
+
getToken: async () => 'your-token',
|
|
795
|
+
supportNetworkRestoredReconnect: true,
|
|
796
|
+
messageCallback: (msg) => {
|
|
797
|
+
// 处理消息
|
|
798
|
+
console.log('收到消息:', msg);
|
|
799
|
+
},
|
|
800
|
+
});
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
onUnmounted(() => {
|
|
804
|
+
im.value?.destroy();
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
const sendMessage = (content: string) => {
|
|
808
|
+
im.value?.sendMessage({
|
|
809
|
+
msgType: 'text',
|
|
810
|
+
content,
|
|
811
|
+
});
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
return {
|
|
815
|
+
sendMessage,
|
|
816
|
+
};
|
|
817
|
+
},
|
|
818
|
+
};
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
---
|
|
822
|
+
|
|
823
|
+
## 常见问题
|
|
824
|
+
|
|
825
|
+
### 1. 如何判断连接是否成功?
|
|
826
|
+
|
|
827
|
+
```
|
|
828
|
+
im.on(EVT_TYPE.CONNECT, () => {
|
|
829
|
+
console.log('连接成功');
|
|
830
|
+
});
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
### 2. 消息发送失败怎么办?
|
|
834
|
+
|
|
835
|
+
消息会自动加入队列,重连后自动重发,无需手动处理。
|
|
836
|
+
|
|
837
|
+
### 3. 如何处理重连失败?
|
|
838
|
+
|
|
839
|
+
```
|
|
840
|
+
im.on(EVT_TYPE.CLOSE, (data) => {
|
|
841
|
+
if (data.code === EXCEPTION_ERROR.NEED_RECONNECT_MANUAL) {
|
|
842
|
+
// 提示用户手动重连
|
|
843
|
+
alert('连接失败,请手动重连');
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
### 4. 如何查看重连进度?
|
|
849
|
+
|
|
850
|
+
```
|
|
851
|
+
im.on(EVT_TYPE.RECONNECT, (data) => {
|
|
852
|
+
console.log(`重连进度: ${data.attempt}/${data.maxRetries}`);
|
|
853
|
+
console.log(`下次重连延迟: ${data.nextRetryDelay}ms`);
|
|
854
|
+
});
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
### 5. 是否支持多实例?
|
|
858
|
+
|
|
859
|
+
支持,每个实例独立管理连接。
|
|
860
|
+
|
|
861
|
+
```
|
|
862
|
+
const im1 = new IM({ ... });
|
|
863
|
+
const im2 = new IM({ ... });
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
### 6. 如何处理 Token 过期?
|
|
867
|
+
|
|
868
|
+
使用 `getToken` 函数动态获取 Token:
|
|
869
|
+
|
|
870
|
+
```
|
|
871
|
+
const im = new IM({
|
|
872
|
+
url: 'wss://example.com/ws',
|
|
873
|
+
appKey: 'your-app-key',
|
|
874
|
+
getToken: async () => {
|
|
875
|
+
// 每次连接时都会调用此函数获取最新 Token
|
|
876
|
+
const res = await fetch('/api/refresh-token');
|
|
877
|
+
return res.json().token;
|
|
878
|
+
},
|
|
879
|
+
});
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
---
|
|
883
|
+
|
|
884
|
+
## 注意事项
|
|
885
|
+
|
|
886
|
+
⚠️ **重要提醒:**
|
|
887
|
+
|
|
888
|
+
1. **网络状态监听**
|
|
889
|
+
如果启用了 `supportNetworkRestoredReconnect`,必须在组件卸载时调用 `destroy()` 方法,否则会有内存泄漏。
|
|
890
|
+
|
|
891
|
+
2. **手动重连**
|
|
892
|
+
`manualReconnect()` 会清空当前队列并重新建立连接,请谨慎使用。
|
|
893
|
+
|
|
894
|
+
3. **消息去重**
|
|
895
|
+
消息去重队列最多保存 100 条消息 key,超过会自动移除最老的。
|
|
896
|
+
|
|
897
|
+
4. **队列大小**
|
|
898
|
+
ActionQueue 最多保存 100 条待发送消息,超过会移除最老的。
|
|
899
|
+
|
|
900
|
+
5. **重连策略**
|
|
901
|
+
重连采用指数退避算法,延迟会逐渐增加:1s → 2s → 4s → 8s → 16s → 30s(最大)
|
|
902
|
+
|
|
903
|
+
6. **事件监听器清理**
|
|
904
|
+
在调用 `destroy()` 方法之前,请确保移除所有手动添加的事件监听器,以避免内存泄漏。虽然SDK会自动清理内部事件监听器,但无法自动清理用户代码中通过 `on()` 方法添加的监听器。
|
|
905
|
+
|
|
906
|
+
---
|
|
907
|
+
|
|
908
|
+
## Changelog
|
|
909
|
+
|
|
910
|
+
详细的变更记录请参阅:[CHANGELOG.md](./CHANGELOG.md)
|
|
911
|
+
|
|
912
|
+
---
|
|
913
|
+
|
|
914
|
+
## License
|
|
915
|
+
|
|
916
|
+
MIT
|
|
917
|
+
|
|
918
|
+
---
|
|
919
|
+
|
|
920
|
+
## 贡献
|
|
921
|
+
|
|
922
|
+
欢迎提交 Issue 和 Pull Request!
|
|
923
|
+
|
|
924
|
+
---
|
|
925
|
+
|
|
926
|
+
## 支持
|
|
927
|
+
|
|
928
|
+
如有问题,请提交 Issue 或联系维护团队。
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "magic-chat-im",
|
|
3
|
+
"version": "2.9.2",
|
|
4
|
+
"license": "ISC",
|
|
5
|
+
"sideEffects": [
|
|
6
|
+
"**/*.scss",
|
|
7
|
+
"**/*.less",
|
|
8
|
+
"**/*.css"
|
|
9
|
+
],
|
|
10
|
+
"main": "es/index.js",
|
|
11
|
+
"unpkg": "dist/magic-chat-im.min.js",
|
|
12
|
+
"module": "es/index.js",
|
|
13
|
+
"typings": "es/index.d.ts",
|
|
14
|
+
"files": [
|
|
15
|
+
"es/",
|
|
16
|
+
"lib/",
|
|
17
|
+
"build",
|
|
18
|
+
"dist/"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"start": "dumi dev",
|
|
22
|
+
"build": "father build",
|
|
23
|
+
"tsc": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"mitt": "^1.2.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"react": "16.14.0",
|
|
30
|
+
"react-dom": "16.14.0",
|
|
31
|
+
"@alife/goc-request": "^3.0.0",
|
|
32
|
+
"@alife/magic-fusion": "^1.9.7",
|
|
33
|
+
"@alife/magic-chat-demo": "workspace:^",
|
|
34
|
+
"@faker-js/faker": "^8.4.0",
|
|
35
|
+
"@testing-library/jest-dom": "^5.17.0",
|
|
36
|
+
"@testing-library/react": "~12.1.5",
|
|
37
|
+
"@testing-library/user-event": "~12.8.3",
|
|
38
|
+
"@types/enzyme": "^3.10.18",
|
|
39
|
+
"@types/jest": "^27.5.2",
|
|
40
|
+
"@types/lodash": "^4.14.202",
|
|
41
|
+
"@types/react": "~16.14.56",
|
|
42
|
+
"@types/react-copy-to-clipboard": "^5.0.7",
|
|
43
|
+
"@types/react-dom": "~16.9.24",
|
|
44
|
+
"babel-jest": "^27.5.1",
|
|
45
|
+
"babel-plugin-import": "^1.13.8",
|
|
46
|
+
"babel-plugin-module-resolver": "^4.1.0",
|
|
47
|
+
"cross-env": "^7.0.3",
|
|
48
|
+
"enzyme": "^3.11.0",
|
|
49
|
+
"enzyme-adapter-react-16": "^1.15.7",
|
|
50
|
+
"enzyme-to-json": "^3.6.2",
|
|
51
|
+
"eslint-plugin-jest": "^26.9.0",
|
|
52
|
+
"father": "^4.5.5",
|
|
53
|
+
"fs-extra": "^11.2.0",
|
|
54
|
+
"identity-obj-proxy": "^3.0.0",
|
|
55
|
+
"jest": "^27.5.1",
|
|
56
|
+
"jest-canvas-mock": "^2.5.2",
|
|
57
|
+
"jest-fetch-mock": "^3.0.3",
|
|
58
|
+
"jest-resolve": "^27.5.1",
|
|
59
|
+
"jscodeshift": "^0.11.0",
|
|
60
|
+
"jsdom": "^19.0.0",
|
|
61
|
+
"npm-run-all": "^4.1.5",
|
|
62
|
+
"react-copy-to-clipboard": "^5.1.0"
|
|
63
|
+
},
|
|
64
|
+
"gitHead": "b79aed52aa2e7d94c72cc7e02b8bd0c58179a7af",
|
|
65
|
+
"repository": "git@gitlab.alibaba-inc.com:magic-design/magic-chat.git"
|
|
66
|
+
}
|