forked from mengyxu/noob-components
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
241 lines
5.0 KiB
241 lines
5.0 KiB
|
3 months ago
|
# WebSocket
|
||
|
|
|
||
|
|
> WebSocket connection management with message handler registration.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
The WebSocket module provides connection management, message queuing, automatic reconnection, and handler registration for different message types.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Architecture
|
||
|
|
|
||
|
|
```
|
||
|
|
WebSocket Module
|
||
|
|
├── websocket (WebSocket instance)
|
||
|
|
├── msgHandlersMap (type -> handlers mapping)
|
||
|
|
├── messageQueue (pending messages)
|
||
|
|
├── openWebSocket() - connect with auto-reconnect
|
||
|
|
├── closeWebSocket() - graceful disconnect
|
||
|
|
├── sendSocketMsg() - send with queue fallback
|
||
|
|
└── registerHandler() - subscribe to message types
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Connection Management
|
||
|
|
|
||
|
|
**File**: `/home/hechang27/Documents/sprt/noob-components/plugs/websocket.ts`
|
||
|
|
|
||
|
|
### State
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
let websocket: WebSocket;
|
||
|
|
const msgHandlersMap = {};
|
||
|
|
let lastUrl;
|
||
|
|
let closeFlag = false;
|
||
|
|
const messageQueue: any[] = [];
|
||
|
|
```
|
||
|
|
|
||
|
|
| Variable | Type | Description |
|
||
|
|
|----------|------|-------------|
|
||
|
|
| `websocket` | WebSocket | Current WebSocket connection |
|
||
|
|
| `msgHandlersMap` | object | Maps message type to handler objects |
|
||
|
|
| `lastUrl` | string | Last connected URL (for reconnect check) |
|
||
|
|
| `closeFlag` | boolean | Manual close flag (vs auto-reconnect) |
|
||
|
|
| `messageQueue` | array | Queued messages when disconnected |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Core Functions
|
||
|
|
|
||
|
|
### openWebSocket
|
||
|
|
|
||
|
|
Connects to WebSocket URL with auto-reconnect on unexpected close.
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export const openWebSocket = (url) => {
|
||
|
|
if (websocket && websocket.readyState === WebSocket.OPEN) {
|
||
|
|
if (lastUrl == url) {
|
||
|
|
return; // Already connected to same URL
|
||
|
|
}
|
||
|
|
closeWebSocket();
|
||
|
|
}
|
||
|
|
websocket = new WebSocket(url);
|
||
|
|
lastUrl = url;
|
||
|
|
|
||
|
|
websocket.onopen = () => {
|
||
|
|
console.log("websocket已连接");
|
||
|
|
closeFlag = true;
|
||
|
|
// Flush queued messages
|
||
|
|
while (messageQueue.length > 0) {
|
||
|
|
const msg = messageQueue.shift();
|
||
|
|
websocket.send(JSON.stringify(msg));
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
websocket.onmessage = (msg) => {
|
||
|
|
const event = JSON.parse(msg.data);
|
||
|
|
const handlers = msgHandlersMap[event.type];
|
||
|
|
if (!handlers) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
Object.values(handlers).forEach((handler: any) => {
|
||
|
|
handler(event.data);
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
websocket.onclose = () => {
|
||
|
|
console.log("websocket已断开");
|
||
|
|
if (closeFlag) {
|
||
|
|
// Auto-reconnect after 2 seconds
|
||
|
|
setTimeout(() => {
|
||
|
|
openWebSocket(url);
|
||
|
|
}, 2000);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### closeWebSocket
|
||
|
|
|
||
|
|
Closes connection and prevents auto-reconnect.
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export const closeWebSocket = () => {
|
||
|
|
closeFlag = false;
|
||
|
|
websocket?.close();
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### sendSocketMsg
|
||
|
|
|
||
|
|
Sends message, queues if not connected.
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export const sendSocketMsg = (msg) => {
|
||
|
|
if (websocket && websocket.readyState === WebSocket.OPEN) {
|
||
|
|
websocket.send(JSON.stringify(msg));
|
||
|
|
} else {
|
||
|
|
messageQueue.push(msg);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### registerHandler
|
||
|
|
|
||
|
|
Registers handler for a message type.
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export const registerHandler = (type, key, handler) => {
|
||
|
|
const handlers = msgHandlersMap[type] || {};
|
||
|
|
handlers[key] = handler;
|
||
|
|
msgHandlersMap[type] = handlers;
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Message Flow
|
||
|
|
|
||
|
|
```
|
||
|
|
1. openWebSocket(url)
|
||
|
|
|
|
||
|
|
v
|
||
|
|
WebSocket.OPEN
|
||
|
|
|
|
||
|
|
v
|
||
|
|
onopen handler
|
||
|
|
|
|
||
|
|
v
|
||
|
|
Flush messageQueue
|
||
|
|
|
|
||
|
|
v
|
||
|
|
2. Server sends message
|
||
|
|
|
|
||
|
|
v
|
||
|
|
onmessage handler
|
||
|
|
|
|
||
|
|
v
|
||
|
|
Parse JSON { type, data }
|
||
|
|
|
|
||
|
|
v
|
||
|
|
Lookup msgHandlersMap[type]
|
||
|
|
|
|
||
|
|
v
|
||
|
|
Call all handlers with data
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Handler Registration Pattern
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Register a handler
|
||
|
|
const myHandler = (data) => {
|
||
|
|
console.log('Received:', data);
|
||
|
|
};
|
||
|
|
|
||
|
|
registerHandler('refresh', 'myComponent', myHandler);
|
||
|
|
|
||
|
|
// Later, unregister by setting to null or removing key
|
||
|
|
delete msgHandlersMap['refresh']['myComponent'];
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Usage Example
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import {
|
||
|
|
openWebSocket,
|
||
|
|
closeWebSocket,
|
||
|
|
sendSocketMsg,
|
||
|
|
registerHandler,
|
||
|
|
} from './websocket';
|
||
|
|
|
||
|
|
// Connect
|
||
|
|
openWebSocket('wss://api.example.com/ws');
|
||
|
|
|
||
|
|
// Subscribe to messages
|
||
|
|
registerHandler('refresh', 'dashboard', (data) => {
|
||
|
|
dashboard.update(data);
|
||
|
|
});
|
||
|
|
|
||
|
|
registerHandler('notification', 'toast', (data) => {
|
||
|
|
showToast(data.message);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Send message
|
||
|
|
sendSocketMsg({ type: 'subscribe', channels: ['refresh', 'notification'] });
|
||
|
|
|
||
|
|
// Cleanup on component unmount
|
||
|
|
onUnmounted(() => {
|
||
|
|
delete msgHandlersMap['refresh']['dashboard'];
|
||
|
|
delete msgHandlersMap['notification']['toast'];
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Auto-Reconnect Behavior
|
||
|
|
|
||
|
|
- **Unexpected disconnect**: `closeFlag = true` causes auto-reconnect after 2 seconds
|
||
|
|
- **Manual close**: `closeWebSocket()` sets `closeFlag = false` to prevent reconnect
|
||
|
|
- **Same URL check**: Connecting to same URL while already connected is a no-op
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Anti-Patterns
|
||
|
|
|
||
|
|
1. **Do not register handlers with duplicate keys** - Each key should be unique per type
|
||
|
|
2. **Do not forget to unregister handlers** - This causes memory leaks and stale handlers
|
||
|
|
3. **Do not assume messages are always JSON** - Always handle parse errors
|
||
|
|
4. **Do not send messages before connection** - Messages are queued but check `readyState` if critical
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Language**: English
|