@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 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
@@ -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
+ }