zsl-im-sdk 1.0.0
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/api/common/common.api.ts +7 -0
- package/api/conversation/conversation.api.ts +93 -0
- package/api/im-login.ts +18 -0
- package/api/message/message.api.ts +51 -0
- package/index.ts +36 -0
- package/jsconfig.json +14 -0
- package/package.json +21 -0
- package/store.ts +692 -0
- package/types/conversation.type.ts +60 -0
- package/types/im-user.type.ts +31 -0
- package/types/index.ts +5 -0
- package/types/message-type.type.ts +94 -0
- package/types/socket-data.type.ts +55 -0
- package/types.ts +66 -0
- package/utils/http.ts +195 -0
- package/websocket.ts +367 -0
package/websocket.ts
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
// 从types目录导入所有类型定义
|
|
2
|
+
export * from "./types";
|
|
3
|
+
import {
|
|
4
|
+
MessageType,
|
|
5
|
+
MessageStatus,
|
|
6
|
+
SocketAction,
|
|
7
|
+
SocketData,
|
|
8
|
+
ISendMessage,
|
|
9
|
+
IMessage,
|
|
10
|
+
IReadReceipt,
|
|
11
|
+
IRecalledMessage,
|
|
12
|
+
} from "./types/index";
|
|
13
|
+
|
|
14
|
+
// 事件类型
|
|
15
|
+
export type EventType =
|
|
16
|
+
| "message"
|
|
17
|
+
| "connect"
|
|
18
|
+
| "disconnect"
|
|
19
|
+
| "error"
|
|
20
|
+
| "reconnect"
|
|
21
|
+
| "recalled";
|
|
22
|
+
|
|
23
|
+
// 事件回调类型
|
|
24
|
+
export type EventCallback<T = any> = (data: T) => void;
|
|
25
|
+
|
|
26
|
+
// 配置选项接口
|
|
27
|
+
export interface IMOptions {
|
|
28
|
+
url: string;
|
|
29
|
+
token: string;
|
|
30
|
+
userId: string | number;
|
|
31
|
+
maxReconnectCount?: number;
|
|
32
|
+
reconnectInterval?: number;
|
|
33
|
+
heartbeatInterval?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 消息队列项接口
|
|
37
|
+
interface MessageQueueItem {
|
|
38
|
+
data: SocketData;
|
|
39
|
+
resolve: () => void;
|
|
40
|
+
reject: (error: any) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// WebSocket实例类型,使用any类型兼容uni-app的SocketTask
|
|
44
|
+
export default class IM {
|
|
45
|
+
private ws: any | null = null;
|
|
46
|
+
private url: string;
|
|
47
|
+
private token: string;
|
|
48
|
+
private userId: string | number;
|
|
49
|
+
private isConnected: boolean = false;
|
|
50
|
+
|
|
51
|
+
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
52
|
+
private reconnectCount: number = 0;
|
|
53
|
+
private maxReconnectCount: number;
|
|
54
|
+
private reconnectInterval: number;
|
|
55
|
+
private messageQueue: MessageQueueItem[] = [];
|
|
56
|
+
private listeners: Record<EventType, EventCallback[]> = {
|
|
57
|
+
message: [],
|
|
58
|
+
connect: [],
|
|
59
|
+
disconnect: [],
|
|
60
|
+
error: [],
|
|
61
|
+
reconnect: [],
|
|
62
|
+
recalled: [],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// 心跳相关
|
|
66
|
+
private heartbeatTimer: ReturnType<typeof setTimeout> | null = null;
|
|
67
|
+
private heartbeatInterval: number;
|
|
68
|
+
|
|
69
|
+
constructor(options: IMOptions) {
|
|
70
|
+
this.url = options.url;
|
|
71
|
+
this.token = options.token;
|
|
72
|
+
this.userId = options.userId;
|
|
73
|
+
this.maxReconnectCount = options.maxReconnectCount || 5;
|
|
74
|
+
this.reconnectInterval = options.reconnectInterval || 3000;
|
|
75
|
+
this.heartbeatInterval = options.heartbeatInterval || 30000;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 获取完整的WebSocket连接URL
|
|
79
|
+
private getWsUrl(): string {
|
|
80
|
+
return `${this.url}?userId=${this.userId}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 连接websocket
|
|
84
|
+
public connect(): Promise<void> {
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
try {
|
|
87
|
+
this.ws = uni.connectSocket({
|
|
88
|
+
url: this.getWsUrl(),
|
|
89
|
+
header: {
|
|
90
|
+
token: this.token,
|
|
91
|
+
},
|
|
92
|
+
success: () => {
|
|
93
|
+
console.log("WebSocket连接请求已发送");
|
|
94
|
+
},
|
|
95
|
+
fail: (err) => {
|
|
96
|
+
console.error("WebSocket连接失败", err);
|
|
97
|
+
reject(err);
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
this.ws.onOpen(() => {
|
|
102
|
+
console.log("WebSocket连接已打开");
|
|
103
|
+
this.isConnected = true;
|
|
104
|
+
this.reconnectCount = 0;
|
|
105
|
+
this.flushMessageQueue();
|
|
106
|
+
this.startHeartbeat();
|
|
107
|
+
this.emit("connect");
|
|
108
|
+
resolve();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
this.ws.onMessage((res: { data: string }) => {
|
|
112
|
+
try {
|
|
113
|
+
const data = JSON.parse(res.data) as SocketData;
|
|
114
|
+
this.handleMessage(data);
|
|
115
|
+
} catch (err: any) {
|
|
116
|
+
console.error("解析WebSocket消息失败", err);
|
|
117
|
+
this.emit("error", err);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
this.ws.onClose(() => {
|
|
122
|
+
console.log("WebSocket连接已关闭");
|
|
123
|
+
this.isConnected = false;
|
|
124
|
+
this.stopHeartbeat();
|
|
125
|
+
this.emit("disconnect");
|
|
126
|
+
this.handleReconnect();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
this.ws.onError((err: any) => {
|
|
130
|
+
console.error("WebSocket错误", err);
|
|
131
|
+
this.isConnected = false;
|
|
132
|
+
this.stopHeartbeat();
|
|
133
|
+
this.emit("error", err);
|
|
134
|
+
});
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.error("WebSocket连接异常", err);
|
|
137
|
+
reject(err);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 断开连接
|
|
143
|
+
public disconnect(): void {
|
|
144
|
+
if (this.ws) {
|
|
145
|
+
this.ws.close();
|
|
146
|
+
this.ws = null;
|
|
147
|
+
}
|
|
148
|
+
this.isConnected = false;
|
|
149
|
+
this.stopHeartbeat();
|
|
150
|
+
if (this.reconnectTimer) {
|
|
151
|
+
clearTimeout(this.reconnectTimer);
|
|
152
|
+
this.reconnectTimer = null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 重连机制
|
|
157
|
+
private handleReconnect(): void {
|
|
158
|
+
if (this.reconnectCount < this.maxReconnectCount) {
|
|
159
|
+
this.reconnectCount++;
|
|
160
|
+
console.log(`正在尝试第${this.reconnectCount}次重连...`);
|
|
161
|
+
this.reconnectTimer = setTimeout(() => {
|
|
162
|
+
this.emit("reconnect", this.reconnectCount);
|
|
163
|
+
this.connect();
|
|
164
|
+
}, this.reconnectInterval);
|
|
165
|
+
} else {
|
|
166
|
+
console.error("重连次数已达上限,停止重连");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 处理收到的消息
|
|
171
|
+
private handleMessage(data: SocketData): void {
|
|
172
|
+
const { action, payload } = data;
|
|
173
|
+
|
|
174
|
+
// 处理心跳消息
|
|
175
|
+
if (action === SocketAction.PING) {
|
|
176
|
+
this.sendPingPong(SocketAction.PONG);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 处理新消息
|
|
181
|
+
// DELIVERED - 已送达
|
|
182
|
+
// MESSAGE - 收到其他人的消息
|
|
183
|
+
if (action === SocketAction.MESSAGE || action === SocketAction.DELIVERED) {
|
|
184
|
+
const payloadData = payload as ISendMessage;
|
|
185
|
+
const message: IMessage = {
|
|
186
|
+
id: payloadData.id || Date.now(),
|
|
187
|
+
conversationId: payloadData.conversationId || "",
|
|
188
|
+
senderId: payloadData.senderId || "",
|
|
189
|
+
recipientId: payloadData.recipientId,
|
|
190
|
+
messageType: payloadData.messageType,
|
|
191
|
+
content: payloadData.content || "",
|
|
192
|
+
mediaUrl: payloadData.mediaUrl || "",
|
|
193
|
+
metadata: payloadData.metadata || "",
|
|
194
|
+
createdAt: payloadData.time || this.getCurrentTime(),
|
|
195
|
+
status: MessageStatus.SENT,
|
|
196
|
+
};
|
|
197
|
+
this.emit("message", message);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 处理消息撤回
|
|
201
|
+
if (action === SocketAction.RECALLED) {
|
|
202
|
+
console.log("========SocketAction.RECALLED==", payload);
|
|
203
|
+
this.emit("recalled", payload);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 处理错误消息
|
|
207
|
+
if (action === SocketAction.ERROR) {
|
|
208
|
+
this.emit("error", payload);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 发送消息
|
|
213
|
+
public send(data: SocketData): Promise<void> {
|
|
214
|
+
return new Promise((resolve, reject) => {
|
|
215
|
+
if (this.isConnected && this.ws) {
|
|
216
|
+
try {
|
|
217
|
+
const message = JSON.stringify(data);
|
|
218
|
+
this.ws.send({
|
|
219
|
+
data: message,
|
|
220
|
+
success: () => {
|
|
221
|
+
console.log("消息发送成功", data);
|
|
222
|
+
resolve();
|
|
223
|
+
},
|
|
224
|
+
fail: (err: any) => {
|
|
225
|
+
console.error("消息发送失败", err);
|
|
226
|
+
reject(err);
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
} catch (err: any) {
|
|
230
|
+
console.error("消息发送异常", err);
|
|
231
|
+
reject(err);
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
console.log("WebSocket未连接,将消息加入队列");
|
|
235
|
+
this.messageQueue.push({ data, resolve, reject });
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// 发送文本消息
|
|
241
|
+
public sendText(
|
|
242
|
+
conversationId: string,
|
|
243
|
+
recipientId: string | number,
|
|
244
|
+
content: string,
|
|
245
|
+
messageType: MessageType = MessageType.TEXT,
|
|
246
|
+
mediaUrl: string | undefined,
|
|
247
|
+
): Promise<void> {
|
|
248
|
+
const payload: ISendMessage = {
|
|
249
|
+
conversationId: conversationId,
|
|
250
|
+
senderId: this.userId,
|
|
251
|
+
recipientId: recipientId,
|
|
252
|
+
messageType: messageType,
|
|
253
|
+
content: content,
|
|
254
|
+
mediaUrl,
|
|
255
|
+
time: this.getCurrentTime(),
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
return this.send({
|
|
259
|
+
action: SocketAction.SEND_MESSAGE,
|
|
260
|
+
payload: payload,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// 发送已读回执
|
|
265
|
+
public sendReadReceipt(
|
|
266
|
+
conversationId: string,
|
|
267
|
+
lastReadMessageId?: number,
|
|
268
|
+
): Promise<void> {
|
|
269
|
+
const payload: IReadReceipt = {
|
|
270
|
+
conversationId: conversationId,
|
|
271
|
+
lastReadMessageId: lastReadMessageId,
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
return this.send({
|
|
275
|
+
action: SocketAction.READ_RECEIPT,
|
|
276
|
+
payload: payload,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 发送消息撤回
|
|
281
|
+
public sendRecallMessage(messageId: number): Promise<void> {
|
|
282
|
+
const payload: IRecalledMessage = {
|
|
283
|
+
messageId,
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
return this.send({
|
|
287
|
+
action: SocketAction.RECALL_MESSAGE,
|
|
288
|
+
payload: payload,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 发送心跳消息
|
|
293
|
+
private sendPingPong(action: SocketAction): Promise<void> {
|
|
294
|
+
return this.send({
|
|
295
|
+
action: action,
|
|
296
|
+
payload: {},
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 开始心跳
|
|
301
|
+
private startHeartbeat(): void {
|
|
302
|
+
this.stopHeartbeat();
|
|
303
|
+
this.heartbeatTimer = setInterval(() => {
|
|
304
|
+
if (this.isConnected) {
|
|
305
|
+
this.sendPingPong(SocketAction.PING);
|
|
306
|
+
}
|
|
307
|
+
}, this.heartbeatInterval);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// 停止心跳
|
|
311
|
+
private stopHeartbeat(): void {
|
|
312
|
+
if (this.heartbeatTimer) {
|
|
313
|
+
clearInterval(this.heartbeatTimer);
|
|
314
|
+
this.heartbeatTimer = null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 刷新消息队列
|
|
319
|
+
private flushMessageQueue(): void {
|
|
320
|
+
if (this.isConnected && this.ws && this.messageQueue.length > 0) {
|
|
321
|
+
const queue = [...this.messageQueue];
|
|
322
|
+
this.messageQueue = [];
|
|
323
|
+
queue.forEach((item) => {
|
|
324
|
+
this.send(item.data).then(item.resolve).catch(item.reject);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// 获取当前时间 (ISO格式)
|
|
330
|
+
private getCurrentTime(): string {
|
|
331
|
+
return new Date().toISOString();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// 事件监听
|
|
335
|
+
public on<T = any>(event: EventType, callback: EventCallback<T>): void {
|
|
336
|
+
if (this.listeners[event]) {
|
|
337
|
+
this.listeners[event].push(callback as EventCallback);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// 移除事件监听
|
|
342
|
+
public off<T = any>(event: EventType, callback: EventCallback<T>): void {
|
|
343
|
+
if (this.listeners[event]) {
|
|
344
|
+
this.listeners[event] = this.listeners[event].filter(
|
|
345
|
+
(cb) => cb !== callback,
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// 触发事件
|
|
351
|
+
private emit<T = any>(event: EventType, data?: T): void {
|
|
352
|
+
if (this.listeners[event]) {
|
|
353
|
+
this.listeners[event].forEach((callback) => {
|
|
354
|
+
try {
|
|
355
|
+
callback(data as any);
|
|
356
|
+
} catch (err) {
|
|
357
|
+
console.error(`事件${event}回调执行失败`, err);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// 获取连接状态
|
|
364
|
+
public getConnectionStatus(): boolean {
|
|
365
|
+
return this.isConnected;
|
|
366
|
+
}
|
|
367
|
+
}
|