@ichaingo/web-socket 1.3.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/README.md +451 -0
- package/dist/index.d.ts +118 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +241 -0
- package/dist/useSocket.d.ts +57 -0
- package/dist/useSocket.d.ts.map +1 -0
- package/dist/useSocket.js +203 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# @ichaingo/socket
|
|
2
|
+
|
|
3
|
+
基于WebSocket的单例模式连接库,提供稳定可靠的WebSocket连接管理功能和React Hooks。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- 🔄 **单例模式**: 确保全局只有一个WebSocket连接实例
|
|
8
|
+
- 🔁 **自动重连**: 连接断开时自动尝试重连,支持配置重连次数和间隔
|
|
9
|
+
- 💓 **心跳检测**: 定期发送心跳包保持连接活跃
|
|
10
|
+
- 📡 **事件驱动**: 支持多种事件监听(连接、消息、错误、状态变化等)
|
|
11
|
+
- 🛡️ **错误处理**: 完善的错误处理和状态管理
|
|
12
|
+
- 📝 **TypeScript**: 完整的TypeScript类型支持
|
|
13
|
+
- 🐛 **调试模式**: 支持调试日志输出
|
|
14
|
+
- ⚛️ **React Hooks**: 提供便捷的React Hooks用于组件集成
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @ichaingo/socket
|
|
20
|
+
# 或
|
|
21
|
+
pnpm add @ichaingo/socket
|
|
22
|
+
# 或
|
|
23
|
+
yarn add @ichaingo/socket
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 快速开始
|
|
27
|
+
|
|
28
|
+
### 基本使用
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import SocketConnection, { ConnectionStatus } from '@ichaingo/socket';
|
|
32
|
+
|
|
33
|
+
// 创建连接实例
|
|
34
|
+
const socket = SocketConnection.getInstance({
|
|
35
|
+
url: 'ws://localhost:8080',
|
|
36
|
+
debug: true
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// 连接WebSocket
|
|
40
|
+
await socket.connect();
|
|
41
|
+
|
|
42
|
+
// 发送消息
|
|
43
|
+
socket.send('Hello WebSocket!');
|
|
44
|
+
socket.sendJSON({ type: 'message', content: 'Hello!' });
|
|
45
|
+
|
|
46
|
+
// 监听消息
|
|
47
|
+
socket.on('message', (data) => {
|
|
48
|
+
console.log('收到消息:', data);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// 监听连接状态变化
|
|
52
|
+
socket.on('statusChange', (status) => {
|
|
53
|
+
console.log('连接状态:', status);
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### React Hooks 使用
|
|
58
|
+
|
|
59
|
+
#### useSocket Hook
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import React from 'react';
|
|
63
|
+
import { useSocket } from '@ichaingo/socket';
|
|
64
|
+
|
|
65
|
+
function ChatComponent() {
|
|
66
|
+
const socket = useSocket({
|
|
67
|
+
url: 'ws://localhost:8080',
|
|
68
|
+
debug: true,
|
|
69
|
+
autoConnect: true
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const [messages, setMessages] = useState<string[]>([]);
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
socket.on('message', (data) => {
|
|
76
|
+
if (typeof data === 'string') {
|
|
77
|
+
setMessages(prev => [...prev, data]);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return () => {
|
|
82
|
+
socket.off('message');
|
|
83
|
+
};
|
|
84
|
+
}, [socket]);
|
|
85
|
+
|
|
86
|
+
const sendMessage = (message: string) => {
|
|
87
|
+
socket.send(message);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div>
|
|
92
|
+
<div>连接状态: {socket.status}</div>
|
|
93
|
+
<div>是否已连接: {socket.isConnected ? '是' : '否'}</div>
|
|
94
|
+
{/* 你的UI组件 */}
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### useSocketMessage Hook
|
|
101
|
+
|
|
102
|
+
专门用于监听特定类型消息的简化Hook:
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import React from 'react';
|
|
106
|
+
import { useSocketMessage } from '@ichaingo/socket';
|
|
107
|
+
|
|
108
|
+
interface ChatMessage {
|
|
109
|
+
user: string;
|
|
110
|
+
content: string;
|
|
111
|
+
timestamp: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function ChatComponent() {
|
|
115
|
+
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
|
116
|
+
|
|
117
|
+
const socket = useSocketMessage<ChatMessage>({
|
|
118
|
+
url: 'ws://localhost:8080/chat',
|
|
119
|
+
debug: true,
|
|
120
|
+
autoConnect: true
|
|
121
|
+
}, (message) => {
|
|
122
|
+
setMessages(prev => [...prev, message]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const sendMessage = (user: string, content: string) => {
|
|
126
|
+
socket.sendJSON({
|
|
127
|
+
user,
|
|
128
|
+
content,
|
|
129
|
+
timestamp: Date.now()
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<div>
|
|
135
|
+
<div>连接状态: {socket.status}</div>
|
|
136
|
+
{/* 你的UI组件 */}
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### useSocketStatus Hook
|
|
143
|
+
|
|
144
|
+
专门用于监听连接状态的简化Hook:
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
import React from 'react';
|
|
148
|
+
import { useSocketStatus } from '@ichaingo/socket';
|
|
149
|
+
|
|
150
|
+
function StatusIndicator() {
|
|
151
|
+
const status = useSocketStatus({
|
|
152
|
+
url: 'ws://localhost:8080',
|
|
153
|
+
debug: true,
|
|
154
|
+
autoConnect: true
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div>
|
|
159
|
+
<div>状态: {status.status}</div>
|
|
160
|
+
<div>是否已连接: {status.isConnected ? '是' : '否'}</div>
|
|
161
|
+
<div>是否正在连接: {status.isConnecting ? '是' : '否'}</div>
|
|
162
|
+
<div>是否正在重连: {status.isReconnecting ? '是' : '否'}</div>
|
|
163
|
+
<div>是否已断开: {status.isDisconnected ? '是' : '否'}</div>
|
|
164
|
+
<div>是否有错误: {status.hasError ? '是' : '否'}</div>
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## API 参考
|
|
171
|
+
|
|
172
|
+
### SocketConnection
|
|
173
|
+
|
|
174
|
+
#### 静态方法
|
|
175
|
+
|
|
176
|
+
- `getInstance(config?: WebSocketConfig): SocketConnection` - 获取单例实例
|
|
177
|
+
|
|
178
|
+
#### 实例方法
|
|
179
|
+
|
|
180
|
+
- `connect(): Promise<void>` - 连接WebSocket
|
|
181
|
+
- `disconnect(): void` - 断开连接
|
|
182
|
+
- `send(data: string | ArrayBuffer | Blob): boolean` - 发送消息
|
|
183
|
+
- `sendJSON(data: any): boolean` - 发送JSON消息
|
|
184
|
+
- `on<K extends keyof WebSocketEvents>(event: K, listener: WebSocketEvents[K]): void` - 添加事件监听器
|
|
185
|
+
- `off<K extends keyof WebSocketEvents>(event: K): void` - 移除事件监听器
|
|
186
|
+
- `getStatus(): ConnectionStatus` - 获取连接状态
|
|
187
|
+
- `isConnected(): boolean` - 检查是否已连接
|
|
188
|
+
- `destroy(): void` - 销毁实例
|
|
189
|
+
|
|
190
|
+
### React Hooks
|
|
191
|
+
|
|
192
|
+
#### useSocket
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
function useSocket(config: UseSocketConfig): UseSocketReturn
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**参数:**
|
|
199
|
+
- `config`: WebSocket配置选项,继承自`WebSocketConfig`并添加了React相关选项
|
|
200
|
+
|
|
201
|
+
**返回值:**
|
|
202
|
+
- `status`: 当前连接状态
|
|
203
|
+
- `isConnected`: 是否已连接
|
|
204
|
+
- `connect`: 连接方法
|
|
205
|
+
- `disconnect`: 断开连接方法
|
|
206
|
+
- `send`: 发送消息方法
|
|
207
|
+
- `sendJSON`: 发送JSON消息方法
|
|
208
|
+
- `on`: 添加事件监听器方法
|
|
209
|
+
- `off`: 移除事件监听器方法
|
|
210
|
+
- `error`: 当前错误状态
|
|
211
|
+
- `reconnectAttempts`: 重连次数
|
|
212
|
+
- `reconnect`: 手动重连方法
|
|
213
|
+
|
|
214
|
+
#### useSocketMessage
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
function useSocketMessage<T>(
|
|
218
|
+
config: UseSocketConfig,
|
|
219
|
+
messageHandler: (data: T) => void
|
|
220
|
+
): UseSocketReturn
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
专门用于监听特定类型消息的简化Hook。
|
|
224
|
+
|
|
225
|
+
#### useSocketStatus
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
function useSocketStatus(config: UseSocketConfig): {
|
|
229
|
+
status: ConnectionStatus;
|
|
230
|
+
isConnected: boolean;
|
|
231
|
+
isConnecting: boolean;
|
|
232
|
+
isReconnecting: boolean;
|
|
233
|
+
isDisconnected: boolean;
|
|
234
|
+
hasError: boolean;
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
专门用于监听连接状态的简化Hook。
|
|
239
|
+
|
|
240
|
+
### 类型定义
|
|
241
|
+
|
|
242
|
+
#### ConnectionStatus
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
enum ConnectionStatus {
|
|
246
|
+
CONNECTING = 'connecting',
|
|
247
|
+
CONNECTED = 'connected',
|
|
248
|
+
DISCONNECTED = 'disconnected',
|
|
249
|
+
RECONNECTING = 'reconnecting',
|
|
250
|
+
ERROR = 'error'
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### WebSocketConfig
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
interface WebSocketConfig {
|
|
258
|
+
url: string; // WebSocket服务器URL
|
|
259
|
+
protocols?: string | string[]; // 子协议
|
|
260
|
+
reconnectInterval?: number; // 重连间隔(默认3000ms)
|
|
261
|
+
maxReconnectAttempts?: number; // 最大重连次数(默认5次)
|
|
262
|
+
heartbeatInterval?: number; // 心跳间隔(默认30000ms)
|
|
263
|
+
debug?: boolean; // 调试模式(默认false)
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
#### UseSocketConfig
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
interface UseSocketConfig extends WebSocketConfig {
|
|
271
|
+
autoConnect?: boolean; // 是否自动连接(默认true)
|
|
272
|
+
reconnectOnMount?: boolean; // 组件挂载时是否重连(默认true)
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
#### WebSocketEvents
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
interface WebSocketEvents {
|
|
280
|
+
open: () => void; // 连接打开
|
|
281
|
+
message: (data: string | ArrayBuffer | Blob) => void; // 收到消息
|
|
282
|
+
close: (event: CloseEvent) => void; // 连接关闭
|
|
283
|
+
error: (error: Event) => void; // 连接错误
|
|
284
|
+
statusChange: (status: ConnectionStatus) => void; // 状态变化
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## 使用示例
|
|
289
|
+
|
|
290
|
+
### 聊天应用
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
import React, { useState, useEffect } from 'react';
|
|
294
|
+
import { useSocket } from '@ichaingo/socket';
|
|
295
|
+
|
|
296
|
+
interface Message {
|
|
297
|
+
id: string;
|
|
298
|
+
user: string;
|
|
299
|
+
content: string;
|
|
300
|
+
timestamp: number;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function ChatApp() {
|
|
304
|
+
const [messages, setMessages] = useState<Message[]>([]);
|
|
305
|
+
const [inputMessage, setInputMessage] = useState('');
|
|
306
|
+
const [username, setUsername] = useState('');
|
|
307
|
+
|
|
308
|
+
const socket = useSocket({
|
|
309
|
+
url: 'ws://localhost:8080/chat',
|
|
310
|
+
debug: true,
|
|
311
|
+
autoConnect: true
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
useEffect(() => {
|
|
315
|
+
socket.on('message', (data) => {
|
|
316
|
+
if (typeof data === 'string') {
|
|
317
|
+
try {
|
|
318
|
+
const message = JSON.parse(data) as Message;
|
|
319
|
+
setMessages(prev => [...prev, message]);
|
|
320
|
+
} catch (err) {
|
|
321
|
+
console.error('Failed to parse message:', err);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
return () => {
|
|
327
|
+
socket.off('message');
|
|
328
|
+
};
|
|
329
|
+
}, [socket]);
|
|
330
|
+
|
|
331
|
+
const sendMessage = () => {
|
|
332
|
+
if (inputMessage.trim() && username.trim()) {
|
|
333
|
+
const message: Message = {
|
|
334
|
+
id: Date.now().toString(),
|
|
335
|
+
user: username,
|
|
336
|
+
content: inputMessage,
|
|
337
|
+
timestamp: Date.now()
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
socket.sendJSON(message);
|
|
341
|
+
setInputMessage('');
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
return (
|
|
346
|
+
<div>
|
|
347
|
+
<div>连接状态: {socket.status}</div>
|
|
348
|
+
|
|
349
|
+
<div>
|
|
350
|
+
<input
|
|
351
|
+
value={username}
|
|
352
|
+
onChange={(e) => setUsername(e.target.value)}
|
|
353
|
+
placeholder="用户名"
|
|
354
|
+
/>
|
|
355
|
+
<input
|
|
356
|
+
value={inputMessage}
|
|
357
|
+
onChange={(e) => setInputMessage(e.target.value)}
|
|
358
|
+
placeholder="输入消息"
|
|
359
|
+
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
|
|
360
|
+
/>
|
|
361
|
+
<button onClick={sendMessage} disabled={!socket.isConnected}>
|
|
362
|
+
发送
|
|
363
|
+
</button>
|
|
364
|
+
</div>
|
|
365
|
+
|
|
366
|
+
<div>
|
|
367
|
+
{messages.map((message) => (
|
|
368
|
+
<div key={message.id}>
|
|
369
|
+
<strong>{message.user}:</strong> {message.content}
|
|
370
|
+
<small> ({new Date(message.timestamp).toLocaleTimeString()})</small>
|
|
371
|
+
</div>
|
|
372
|
+
))}
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### 实时通知系统
|
|
380
|
+
|
|
381
|
+
```tsx
|
|
382
|
+
import React, { useState } from 'react';
|
|
383
|
+
import { useSocketMessage } from '@ichaingo/socket';
|
|
384
|
+
|
|
385
|
+
interface Notification {
|
|
386
|
+
id: string;
|
|
387
|
+
type: 'info' | 'warning' | 'error';
|
|
388
|
+
title: string;
|
|
389
|
+
content: string;
|
|
390
|
+
timestamp: number;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function NotificationSystem() {
|
|
394
|
+
const [notifications, setNotifications] = useState<Notification[]>([]);
|
|
395
|
+
|
|
396
|
+
const socket = useSocketMessage<Notification>({
|
|
397
|
+
url: 'ws://localhost:8080/notifications',
|
|
398
|
+
debug: true,
|
|
399
|
+
autoConnect: true
|
|
400
|
+
}, (notification) => {
|
|
401
|
+
setNotifications(prev => [notification, ...prev]);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
const clearNotifications = () => {
|
|
405
|
+
setNotifications([]);
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
return (
|
|
409
|
+
<div>
|
|
410
|
+
<div>连接状态: {socket.status}</div>
|
|
411
|
+
<button onClick={clearNotifications}>清空通知</button>
|
|
412
|
+
|
|
413
|
+
<div>
|
|
414
|
+
{notifications.map((notification) => (
|
|
415
|
+
<div
|
|
416
|
+
key={notification.id}
|
|
417
|
+
style={{
|
|
418
|
+
padding: '10px',
|
|
419
|
+
margin: '5px 0',
|
|
420
|
+
border: '1px solid #ccc',
|
|
421
|
+
borderRadius: '4px',
|
|
422
|
+
backgroundColor: notification.type === 'error' ? '#ffebee' :
|
|
423
|
+
notification.type === 'warning' ? '#fff3e0' : '#e3f2fd'
|
|
424
|
+
}}
|
|
425
|
+
>
|
|
426
|
+
<strong>{notification.title}</strong>
|
|
427
|
+
<div>{notification.content}</div>
|
|
428
|
+
<small>{new Date(notification.timestamp).toLocaleString()}</small>
|
|
429
|
+
</div>
|
|
430
|
+
))}
|
|
431
|
+
</div>
|
|
432
|
+
</div>
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## 构建
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
nx build socket
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
## 测试
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
nx test socket
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
## 许可证
|
|
450
|
+
|
|
451
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket连接状态枚举
|
|
3
|
+
*/
|
|
4
|
+
export declare enum ConnectionStatus {
|
|
5
|
+
CONNECTING = "connecting",
|
|
6
|
+
CONNECTED = "connected",
|
|
7
|
+
DISCONNECTED = "disconnected",
|
|
8
|
+
RECONNECTING = "reconnecting",
|
|
9
|
+
ERROR = "error"
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* WebSocket事件类型
|
|
13
|
+
*/
|
|
14
|
+
export interface WebSocketEvents {
|
|
15
|
+
open: () => void;
|
|
16
|
+
message: (data: string | ArrayBuffer | Blob) => void;
|
|
17
|
+
close: (event: CloseEvent) => void;
|
|
18
|
+
error: (error: Event) => void;
|
|
19
|
+
statusChange: (status: ConnectionStatus) => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* WebSocket配置选项
|
|
23
|
+
*/
|
|
24
|
+
export interface WebSocketConfig {
|
|
25
|
+
url: string;
|
|
26
|
+
protocols?: string | string[];
|
|
27
|
+
reconnectInterval?: number;
|
|
28
|
+
maxReconnectAttempts?: number;
|
|
29
|
+
heartbeatInterval?: number;
|
|
30
|
+
debug?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* WebSocket单例连接类
|
|
34
|
+
* 提供WebSocket连接的创建、管理、重连等功能
|
|
35
|
+
*/
|
|
36
|
+
declare class SocketConnection {
|
|
37
|
+
private static instance;
|
|
38
|
+
private socket;
|
|
39
|
+
private config;
|
|
40
|
+
private status;
|
|
41
|
+
private reconnectAttempts;
|
|
42
|
+
private reconnectTimer;
|
|
43
|
+
private heartbeatTimer;
|
|
44
|
+
private eventListeners;
|
|
45
|
+
private constructor();
|
|
46
|
+
/**
|
|
47
|
+
* 获取单例实例
|
|
48
|
+
*/
|
|
49
|
+
static getInstance(config?: WebSocketConfig): SocketConnection;
|
|
50
|
+
/**
|
|
51
|
+
* 连接WebSocket
|
|
52
|
+
*/
|
|
53
|
+
connect(): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* 断开连接
|
|
56
|
+
*/
|
|
57
|
+
disconnect(): void;
|
|
58
|
+
/**
|
|
59
|
+
* 发送消息
|
|
60
|
+
*/
|
|
61
|
+
send(data: string | ArrayBuffer | Blob): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* 发送JSON消息
|
|
64
|
+
*/
|
|
65
|
+
sendJSON(data: unknown): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* 添加事件监听器
|
|
68
|
+
*/
|
|
69
|
+
on<K extends keyof WebSocketEvents>(event: K, listener: WebSocketEvents[K]): void;
|
|
70
|
+
/**
|
|
71
|
+
* 移除事件监听器
|
|
72
|
+
*/
|
|
73
|
+
off<K extends keyof WebSocketEvents>(event: K): void;
|
|
74
|
+
/**
|
|
75
|
+
* 获取连接状态
|
|
76
|
+
*/
|
|
77
|
+
getStatus(): ConnectionStatus;
|
|
78
|
+
/**
|
|
79
|
+
* 检查是否已连接
|
|
80
|
+
*/
|
|
81
|
+
isConnected(): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* 设置事件监听器
|
|
84
|
+
*/
|
|
85
|
+
private setupEventListeners;
|
|
86
|
+
/**
|
|
87
|
+
* 设置连接状态
|
|
88
|
+
*/
|
|
89
|
+
private setStatus;
|
|
90
|
+
/**
|
|
91
|
+
* 安排重连
|
|
92
|
+
*/
|
|
93
|
+
private scheduleReconnect;
|
|
94
|
+
/**
|
|
95
|
+
* 清除重连定时器
|
|
96
|
+
*/
|
|
97
|
+
private clearReconnectTimer;
|
|
98
|
+
/**
|
|
99
|
+
* 开始心跳检测
|
|
100
|
+
*/
|
|
101
|
+
private startHeartbeat;
|
|
102
|
+
/**
|
|
103
|
+
* 停止心跳检测
|
|
104
|
+
*/
|
|
105
|
+
private stopHeartbeat;
|
|
106
|
+
/**
|
|
107
|
+
* 日志输出
|
|
108
|
+
*/
|
|
109
|
+
private log;
|
|
110
|
+
/**
|
|
111
|
+
* 销毁实例
|
|
112
|
+
*/
|
|
113
|
+
destroy(): void;
|
|
114
|
+
}
|
|
115
|
+
export default SocketConnection;
|
|
116
|
+
export { useSocket, useSocketMessage, useSocketStatus } from './useSocket.js';
|
|
117
|
+
export type { UseSocketConfig, UseSocketReturn } from './useSocket.js';
|
|
118
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,oBAAY,gBAAgB;IAC1B,UAAU,eAAe;IACzB,SAAS,cAAc;IACvB,YAAY,iBAAiB;IAC7B,YAAY,iBAAiB;IAC7B,KAAK,UAAU;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IACrD,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACnC,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAC9B,YAAY,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAClD;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,cAAM,gBAAgB;IACpB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAiC;IACxD,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,MAAM,CAAmD;IACjE,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,cAAc,CAAgC;IAEtD,OAAO;IAUP;;OAEG;WACW,WAAW,CAAC,MAAM,CAAC,EAAE,eAAe,GAAG,gBAAgB;IAUrE;;OAEG;IACI,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAoC/B;;OAEG;IACI,UAAU,IAAI,IAAI;IAazB;;OAEG;IACI,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,GAAG,OAAO;IAWvD;;OAEG;IACI,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO;IAIvC;;OAEG;IACI,EAAE,CAAC,CAAC,SAAS,MAAM,eAAe,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,IAAI;IAIxF;;OAEG;IACI,GAAG,CAAC,CAAC,SAAS,MAAM,eAAe,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAI3D;;OAEG;IACI,SAAS,IAAI,gBAAgB;IAIpC;;OAEG;IACI,WAAW,IAAI,OAAO;IAI7B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA2B3B;;OAEG;IACH,OAAO,CAAC,SAAS;IAQjB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAezB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAO3B;;OAEG;IACH,OAAO,CAAC,cAAc;IAYtB;;OAEG;IACH,OAAO,CAAC,aAAa;IAOrB;;OAEG;IACH,OAAO,CAAC,GAAG;IAMX;;OAEG;IACI,OAAO,IAAI,IAAI;CAKvB;AAED,eAAe,gBAAgB,CAAC;AAGhC,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC9E,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket连接状态枚举
|
|
3
|
+
*/
|
|
4
|
+
export var ConnectionStatus;
|
|
5
|
+
(function (ConnectionStatus) {
|
|
6
|
+
ConnectionStatus["CONNECTING"] = "connecting";
|
|
7
|
+
ConnectionStatus["CONNECTED"] = "connected";
|
|
8
|
+
ConnectionStatus["DISCONNECTED"] = "disconnected";
|
|
9
|
+
ConnectionStatus["RECONNECTING"] = "reconnecting";
|
|
10
|
+
ConnectionStatus["ERROR"] = "error";
|
|
11
|
+
})(ConnectionStatus || (ConnectionStatus = {}));
|
|
12
|
+
/**
|
|
13
|
+
* WebSocket单例连接类
|
|
14
|
+
* 提供WebSocket连接的创建、管理、重连等功能
|
|
15
|
+
*/
|
|
16
|
+
class SocketConnection {
|
|
17
|
+
static instance = null;
|
|
18
|
+
socket = null;
|
|
19
|
+
config;
|
|
20
|
+
status = ConnectionStatus.DISCONNECTED;
|
|
21
|
+
reconnectAttempts = 0;
|
|
22
|
+
reconnectTimer = null;
|
|
23
|
+
heartbeatTimer = null;
|
|
24
|
+
eventListeners = {};
|
|
25
|
+
constructor(config) {
|
|
26
|
+
this.config = {
|
|
27
|
+
reconnectInterval: 3000,
|
|
28
|
+
maxReconnectAttempts: 5,
|
|
29
|
+
heartbeatInterval: 30000,
|
|
30
|
+
debug: false,
|
|
31
|
+
...config
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 获取单例实例
|
|
36
|
+
*/
|
|
37
|
+
static getInstance(config) {
|
|
38
|
+
if (!SocketConnection.instance) {
|
|
39
|
+
if (!config) {
|
|
40
|
+
throw new Error('首次创建SocketConnection实例时必须提供配置');
|
|
41
|
+
}
|
|
42
|
+
SocketConnection.instance = new SocketConnection(config);
|
|
43
|
+
}
|
|
44
|
+
return SocketConnection.instance;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 连接WebSocket
|
|
48
|
+
*/
|
|
49
|
+
connect() {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
52
|
+
resolve();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.setStatus(ConnectionStatus.CONNECTING);
|
|
56
|
+
this.log('正在连接WebSocket...', this.config.url);
|
|
57
|
+
try {
|
|
58
|
+
this.socket = new WebSocket(this.config.url, this.config.protocols);
|
|
59
|
+
this.setupEventListeners();
|
|
60
|
+
this.socket.onopen = () => {
|
|
61
|
+
this.log('WebSocket连接成功');
|
|
62
|
+
this.setStatus(ConnectionStatus.CONNECTED);
|
|
63
|
+
this.reconnectAttempts = 0;
|
|
64
|
+
this.startHeartbeat();
|
|
65
|
+
this.eventListeners.open?.();
|
|
66
|
+
resolve();
|
|
67
|
+
};
|
|
68
|
+
this.socket.onerror = (error) => {
|
|
69
|
+
this.log('WebSocket连接错误:', error);
|
|
70
|
+
this.setStatus(ConnectionStatus.ERROR);
|
|
71
|
+
this.eventListeners.error?.(error);
|
|
72
|
+
reject(error);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
this.log('创建WebSocket连接失败:', error);
|
|
77
|
+
this.setStatus(ConnectionStatus.ERROR);
|
|
78
|
+
reject(error);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 断开连接
|
|
84
|
+
*/
|
|
85
|
+
disconnect() {
|
|
86
|
+
this.log('正在断开WebSocket连接...');
|
|
87
|
+
this.stopHeartbeat();
|
|
88
|
+
this.clearReconnectTimer();
|
|
89
|
+
if (this.socket) {
|
|
90
|
+
this.socket.close(1000, '主动断开连接');
|
|
91
|
+
this.socket = null;
|
|
92
|
+
}
|
|
93
|
+
this.setStatus(ConnectionStatus.DISCONNECTED);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 发送消息
|
|
97
|
+
*/
|
|
98
|
+
send(data) {
|
|
99
|
+
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
100
|
+
this.socket.send(data);
|
|
101
|
+
this.log('发送消息:', data);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
this.log('WebSocket未连接,无法发送消息');
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 发送JSON消息
|
|
111
|
+
*/
|
|
112
|
+
sendJSON(data) {
|
|
113
|
+
return this.send(JSON.stringify(data));
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 添加事件监听器
|
|
117
|
+
*/
|
|
118
|
+
on(event, listener) {
|
|
119
|
+
this.eventListeners[event] = listener;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 移除事件监听器
|
|
123
|
+
*/
|
|
124
|
+
off(event) {
|
|
125
|
+
delete this.eventListeners[event];
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 获取连接状态
|
|
129
|
+
*/
|
|
130
|
+
getStatus() {
|
|
131
|
+
return this.status;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 检查是否已连接
|
|
135
|
+
*/
|
|
136
|
+
isConnected() {
|
|
137
|
+
return this.status === ConnectionStatus.CONNECTED && this.socket?.readyState === WebSocket.OPEN;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 设置事件监听器
|
|
141
|
+
*/
|
|
142
|
+
setupEventListeners() {
|
|
143
|
+
if (!this.socket)
|
|
144
|
+
return;
|
|
145
|
+
this.socket.onmessage = (event) => {
|
|
146
|
+
this.log('收到消息:', event.data);
|
|
147
|
+
this.eventListeners.message?.(event.data);
|
|
148
|
+
};
|
|
149
|
+
this.socket.onclose = (event) => {
|
|
150
|
+
this.log('WebSocket连接关闭:', event.code, event.reason);
|
|
151
|
+
this.setStatus(ConnectionStatus.DISCONNECTED);
|
|
152
|
+
this.stopHeartbeat();
|
|
153
|
+
this.eventListeners.close?.(event);
|
|
154
|
+
// 如果不是主动断开,尝试重连
|
|
155
|
+
if (event.code !== 1000 && this.reconnectAttempts < (this.config.maxReconnectAttempts || 5)) {
|
|
156
|
+
this.scheduleReconnect();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
this.socket.onerror = (error) => {
|
|
160
|
+
this.log('WebSocket错误:', error);
|
|
161
|
+
this.setStatus(ConnectionStatus.ERROR);
|
|
162
|
+
this.eventListeners.error?.(error);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 设置连接状态
|
|
167
|
+
*/
|
|
168
|
+
setStatus(status) {
|
|
169
|
+
if (this.status !== status) {
|
|
170
|
+
this.status = status;
|
|
171
|
+
this.log('连接状态变更:', status);
|
|
172
|
+
this.eventListeners.statusChange?.(status);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 安排重连
|
|
177
|
+
*/
|
|
178
|
+
scheduleReconnect() {
|
|
179
|
+
if (this.reconnectTimer)
|
|
180
|
+
return;
|
|
181
|
+
this.reconnectAttempts++;
|
|
182
|
+
this.setStatus(ConnectionStatus.RECONNECTING);
|
|
183
|
+
this.log(`准备重连 (第${this.reconnectAttempts}次)...`);
|
|
184
|
+
this.reconnectTimer = setTimeout(() => {
|
|
185
|
+
this.reconnectTimer = null;
|
|
186
|
+
this.connect().catch(() => {
|
|
187
|
+
// 重连失败,继续尝试
|
|
188
|
+
});
|
|
189
|
+
}, this.config.reconnectInterval || 3000);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* 清除重连定时器
|
|
193
|
+
*/
|
|
194
|
+
clearReconnectTimer() {
|
|
195
|
+
if (this.reconnectTimer) {
|
|
196
|
+
clearTimeout(this.reconnectTimer);
|
|
197
|
+
this.reconnectTimer = null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* 开始心跳检测
|
|
202
|
+
*/
|
|
203
|
+
startHeartbeat() {
|
|
204
|
+
this.stopHeartbeat();
|
|
205
|
+
if (this.config.heartbeatInterval && this.config.heartbeatInterval > 0) {
|
|
206
|
+
this.heartbeatTimer = setInterval(() => {
|
|
207
|
+
if (this.isConnected()) {
|
|
208
|
+
this.send('ping');
|
|
209
|
+
}
|
|
210
|
+
}, this.config.heartbeatInterval);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* 停止心跳检测
|
|
215
|
+
*/
|
|
216
|
+
stopHeartbeat() {
|
|
217
|
+
if (this.heartbeatTimer) {
|
|
218
|
+
clearInterval(this.heartbeatTimer);
|
|
219
|
+
this.heartbeatTimer = null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* 日志输出
|
|
224
|
+
*/
|
|
225
|
+
log(...args) {
|
|
226
|
+
if (this.config.debug) {
|
|
227
|
+
console.log('[SocketConnection]', ...args);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* 销毁实例
|
|
232
|
+
*/
|
|
233
|
+
destroy() {
|
|
234
|
+
this.disconnect();
|
|
235
|
+
this.eventListeners = {};
|
|
236
|
+
SocketConnection.instance = null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
export default SocketConnection;
|
|
240
|
+
// 导出React Hooks
|
|
241
|
+
export { useSocket, useSocketMessage, useSocketStatus } from './useSocket.js';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ConnectionStatus, WebSocketConfig, WebSocketEvents } from './index.js';
|
|
2
|
+
/**
|
|
3
|
+
* useSocket Hook 的配置选项
|
|
4
|
+
*/
|
|
5
|
+
export interface UseSocketConfig extends WebSocketConfig {
|
|
6
|
+
autoConnect?: boolean;
|
|
7
|
+
reconnectOnMount?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* useSocket Hook 的返回值
|
|
11
|
+
*/
|
|
12
|
+
export interface UseSocketReturn {
|
|
13
|
+
status: ConnectionStatus;
|
|
14
|
+
isConnected: boolean;
|
|
15
|
+
connect: () => Promise<void>;
|
|
16
|
+
disconnect: () => void;
|
|
17
|
+
send: (data: string | ArrayBuffer | Blob) => boolean;
|
|
18
|
+
sendJSON: (data: unknown) => boolean;
|
|
19
|
+
on: <K extends keyof WebSocketEvents>(event: K, listener: WebSocketEvents[K]) => void;
|
|
20
|
+
off: <K extends keyof WebSocketEvents>(event: K) => void;
|
|
21
|
+
error: Event | null;
|
|
22
|
+
reconnectAttempts: number;
|
|
23
|
+
reconnect: () => void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* useSocket Hook
|
|
27
|
+
* 提供在React组件中使用WebSocket连接的便捷方法
|
|
28
|
+
*
|
|
29
|
+
* @param config WebSocket配置选项
|
|
30
|
+
* @returns useSocket的返回值对象
|
|
31
|
+
*/
|
|
32
|
+
export declare function useSocket(config: UseSocketConfig): UseSocketReturn;
|
|
33
|
+
/**
|
|
34
|
+
* useSocketMessage Hook
|
|
35
|
+
* 专门用于监听特定类型消息的简化Hook
|
|
36
|
+
*
|
|
37
|
+
* @param config WebSocket配置选项
|
|
38
|
+
* @param messageHandler 消息处理函数
|
|
39
|
+
* @returns useSocket的返回值对象
|
|
40
|
+
*/
|
|
41
|
+
export declare function useSocketMessage<T = any>(config: UseSocketConfig, messageHandler: (data: T) => void): UseSocketReturn;
|
|
42
|
+
/**
|
|
43
|
+
* useSocketStatus Hook
|
|
44
|
+
* 专门用于监听连接状态的简化Hook
|
|
45
|
+
*
|
|
46
|
+
* @param config WebSocket配置选项
|
|
47
|
+
* @returns 连接状态和状态变化回调
|
|
48
|
+
*/
|
|
49
|
+
export declare function useSocketStatus(config: UseSocketConfig): {
|
|
50
|
+
status: ConnectionStatus;
|
|
51
|
+
isConnected: boolean;
|
|
52
|
+
isConnecting: boolean;
|
|
53
|
+
isReconnecting: boolean;
|
|
54
|
+
isDisconnected: boolean;
|
|
55
|
+
hasError: boolean;
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=useSocket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSocket.d.ts","sourceRoot":"","sources":["../src/useSocket.ts"],"names":[],"mappings":"AACA,OAAyB,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElG;;GAEG;AACH,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACtD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAE9B,MAAM,EAAE,gBAAgB,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;IAGrB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,UAAU,EAAE,MAAM,IAAI,CAAC;IAGvB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,KAAK,OAAO,CAAC;IACrD,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;IAGrC,EAAE,EAAE,CAAC,CAAC,SAAS,MAAM,eAAe,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IACtF,GAAG,EAAE,CAAC,CAAC,SAAS,MAAM,eAAe,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAGzD,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAGpB,iBAAiB,EAAE,MAAM,CAAC;IAG1B,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAiKlE;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,GAAG,EACtC,MAAM,EAAE,eAAe,EACvB,cAAc,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,GAChC,eAAe,CAmBjB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe;;;;;;;EAmBtD"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
2
|
+
import SocketConnection, { ConnectionStatus } from './index.js';
|
|
3
|
+
/**
|
|
4
|
+
* useSocket Hook
|
|
5
|
+
* 提供在React组件中使用WebSocket连接的便捷方法
|
|
6
|
+
*
|
|
7
|
+
* @param config WebSocket配置选项
|
|
8
|
+
* @returns useSocket的返回值对象
|
|
9
|
+
*/
|
|
10
|
+
export function useSocket(config) {
|
|
11
|
+
const [status, setStatus] = useState(ConnectionStatus.DISCONNECTED);
|
|
12
|
+
const [error, setError] = useState(null);
|
|
13
|
+
const [reconnectAttempts, setReconnectAttempts] = useState(0);
|
|
14
|
+
const socketRef = useRef(null);
|
|
15
|
+
const isInitializedRef = useRef(false);
|
|
16
|
+
const configRef = useRef(config);
|
|
17
|
+
// 更新配置引用
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
configRef.current = config;
|
|
20
|
+
}, [config]);
|
|
21
|
+
// 获取或创建socket实例
|
|
22
|
+
const getSocket = useCallback(() => {
|
|
23
|
+
if (!socketRef.current) {
|
|
24
|
+
try {
|
|
25
|
+
socketRef.current = SocketConnection.getInstance(configRef.current);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
console.error('Failed to create socket instance:', err);
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return socketRef.current;
|
|
33
|
+
}, []);
|
|
34
|
+
// 连接WebSocket
|
|
35
|
+
const connect = useCallback(async () => {
|
|
36
|
+
try {
|
|
37
|
+
setError(null);
|
|
38
|
+
const socket = getSocket();
|
|
39
|
+
await socket.connect();
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
const errorEvent = err;
|
|
43
|
+
setError(errorEvent);
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
}, [getSocket]);
|
|
47
|
+
// 断开连接
|
|
48
|
+
const disconnect = useCallback(() => {
|
|
49
|
+
const socket = socketRef.current;
|
|
50
|
+
if (socket) {
|
|
51
|
+
socket.disconnect();
|
|
52
|
+
}
|
|
53
|
+
}, []);
|
|
54
|
+
// 发送消息
|
|
55
|
+
const send = useCallback((data) => {
|
|
56
|
+
const socket = socketRef.current;
|
|
57
|
+
if (socket) {
|
|
58
|
+
return socket.send(data);
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}, []);
|
|
62
|
+
// 发送JSON消息
|
|
63
|
+
const sendJSON = useCallback((data) => {
|
|
64
|
+
const socket = socketRef.current;
|
|
65
|
+
if (socket) {
|
|
66
|
+
return socket.sendJSON(data);
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}, []);
|
|
70
|
+
// 添加事件监听器
|
|
71
|
+
const on = useCallback((event, listener) => {
|
|
72
|
+
const socket = socketRef.current;
|
|
73
|
+
if (socket) {
|
|
74
|
+
socket.on(event, listener);
|
|
75
|
+
}
|
|
76
|
+
}, []);
|
|
77
|
+
// 移除事件监听器
|
|
78
|
+
const off = useCallback((event) => {
|
|
79
|
+
const socket = socketRef.current;
|
|
80
|
+
if (socket) {
|
|
81
|
+
socket.off(event);
|
|
82
|
+
}
|
|
83
|
+
}, []);
|
|
84
|
+
// 手动重连
|
|
85
|
+
const reconnect = useCallback(async () => {
|
|
86
|
+
disconnect();
|
|
87
|
+
await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒
|
|
88
|
+
await connect();
|
|
89
|
+
}, [disconnect, connect]);
|
|
90
|
+
// 初始化socket和事件监听
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (isInitializedRef.current)
|
|
93
|
+
return;
|
|
94
|
+
try {
|
|
95
|
+
const socket = getSocket();
|
|
96
|
+
// 监听状态变化
|
|
97
|
+
socket.on('statusChange', (newStatus) => {
|
|
98
|
+
setStatus(newStatus);
|
|
99
|
+
// 更新重连次数
|
|
100
|
+
if (newStatus === ConnectionStatus.RECONNECTING) {
|
|
101
|
+
setReconnectAttempts(prev => prev + 1);
|
|
102
|
+
}
|
|
103
|
+
else if (newStatus === ConnectionStatus.CONNECTED) {
|
|
104
|
+
setReconnectAttempts(0);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
// 监听错误
|
|
108
|
+
socket.on('error', (errorEvent) => {
|
|
109
|
+
setError(errorEvent);
|
|
110
|
+
});
|
|
111
|
+
// 监听连接关闭
|
|
112
|
+
socket.on('close', () => {
|
|
113
|
+
setError(null);
|
|
114
|
+
});
|
|
115
|
+
isInitializedRef.current = true;
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
console.error('Failed to initialize socket:', err);
|
|
119
|
+
setError(err);
|
|
120
|
+
}
|
|
121
|
+
}, [getSocket]);
|
|
122
|
+
// 自动连接
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (config.autoConnect !== false && !isInitializedRef.current) {
|
|
125
|
+
connect().catch(console.error);
|
|
126
|
+
}
|
|
127
|
+
}, [config.autoConnect, connect]);
|
|
128
|
+
// 组件卸载时清理
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
return () => {
|
|
131
|
+
const socket = socketRef.current;
|
|
132
|
+
if (socket && config.autoConnect !== false) {
|
|
133
|
+
socket.disconnect();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}, [config.autoConnect]);
|
|
137
|
+
// 检查是否已连接
|
|
138
|
+
const isConnected = status === ConnectionStatus.CONNECTED;
|
|
139
|
+
return {
|
|
140
|
+
status,
|
|
141
|
+
isConnected,
|
|
142
|
+
connect,
|
|
143
|
+
disconnect,
|
|
144
|
+
send,
|
|
145
|
+
sendJSON,
|
|
146
|
+
on,
|
|
147
|
+
off,
|
|
148
|
+
error,
|
|
149
|
+
reconnectAttempts,
|
|
150
|
+
reconnect,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* useSocketMessage Hook
|
|
155
|
+
* 专门用于监听特定类型消息的简化Hook
|
|
156
|
+
*
|
|
157
|
+
* @param config WebSocket配置选项
|
|
158
|
+
* @param messageHandler 消息处理函数
|
|
159
|
+
* @returns useSocket的返回值对象
|
|
160
|
+
*/
|
|
161
|
+
export function useSocketMessage(config, messageHandler) {
|
|
162
|
+
const socket = useSocket(config);
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
socket.on('message', (data) => {
|
|
165
|
+
try {
|
|
166
|
+
const parsedData = typeof data === 'string' ? JSON.parse(data) : data;
|
|
167
|
+
messageHandler(parsedData);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
console.error('Failed to parse message:', err);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
return () => {
|
|
174
|
+
socket.off('message');
|
|
175
|
+
};
|
|
176
|
+
}, [socket, messageHandler]);
|
|
177
|
+
return socket;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* useSocketStatus Hook
|
|
181
|
+
* 专门用于监听连接状态的简化Hook
|
|
182
|
+
*
|
|
183
|
+
* @param config WebSocket配置选项
|
|
184
|
+
* @returns 连接状态和状态变化回调
|
|
185
|
+
*/
|
|
186
|
+
export function useSocketStatus(config) {
|
|
187
|
+
const [status, setStatus] = useState(ConnectionStatus.DISCONNECTED);
|
|
188
|
+
const socket = useSocket(config);
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
socket.on('statusChange', setStatus);
|
|
191
|
+
return () => {
|
|
192
|
+
socket.off('statusChange');
|
|
193
|
+
};
|
|
194
|
+
}, [socket]);
|
|
195
|
+
return {
|
|
196
|
+
status,
|
|
197
|
+
isConnected: status === ConnectionStatus.CONNECTED,
|
|
198
|
+
isConnecting: status === ConnectionStatus.CONNECTING,
|
|
199
|
+
isReconnecting: status === ConnectionStatus.RECONNECTING,
|
|
200
|
+
isDisconnected: status === ConnectionStatus.DISCONNECTED,
|
|
201
|
+
hasError: status === ConnectionStatus.ERROR,
|
|
202
|
+
};
|
|
203
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ichaingo/web-socket",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./package.json": "./package.json",
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"!**/*.tsbuildinfo"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"react": ">=16.8.0"
|
|
23
|
+
},
|
|
24
|
+
"peerDependenciesMeta": {
|
|
25
|
+
"react": {
|
|
26
|
+
"optional": false
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|