Jerry 4a82585c29 关闭未认证的WebSocket连接并重构Redis服务
在WebSocket认证失败时,主动关闭连接以提高安全性。重构了Redis服务中的方法,使其更加通用,并更新了相关的导入和调用。同时,调整了数据持久化路径的生成方式,支持自定义文件名。
2025-04-17 13:36:41 +08:00

101 lines
3.4 KiB
TypeScript

import WebSocket, { WebSocketServer } from 'ws';
import config from '../../utils/core/config';
import logger from '../../utils/core/logger';
import { AuthenticatedSocket, AuthMessage, WSMessage } from '../../types/ws';
import WsTools from '../../utils/ws/wsTools';
import wsHandler from './handler';
import { clearInterval } from 'node:timers';
import wsClientManager from './wsClientManager';
class WSServer {
private readonly wss: WebSocketServer;
private readonly port = Number(config.get('WS_PORT'));
private readonly secret = config.get('WS_SECRET');
constructor() {
this.wss = new WebSocketServer({ port: this.port });
this.init();
logger.info(`WS Server listening on ws://localhost:${this.port}`);
}
private init(): void {
this.wss.on('connection', (socket: AuthenticatedSocket, req) => {
const ip = req.socket.remoteAddress || 'unknown';
logger.info(`收到来自 ${ip} 的 WebSocket 连接请求..`);
socket.heartbeat = WsTools.setUpHeartbeat(socket);
socket.on('message', async (raw) => {
logger.debug(`Received raw message from ${ip}: ${raw.toString()}`);
const msg = WsTools.parseMessage<WSMessage>(raw);
if (!msg) return this.handleInvalidMessage(socket, ip);
await this.routeMessage(socket, msg, ip);
});
socket.on('close', () => {
logger.info(`ws断开连接 ${ip} (${socket.clientId || 'unauthenticated'})`);
this.handleDisconnect(socket);
});
socket.on('error', (err) => {
logger.error(`WS error from ${ip}: ${err.message}`);
});
});
}
private async handleInvalidMessage(socket: WebSocket, ip: string) {
logger.warn(`Invalid message received from ${ip}`);
await WsTools.send(socket, {
type: 'error',
message: 'Invalid message format',
});
}
private async routeMessage(socket: AuthenticatedSocket, msg: WSMessage, ip: string) {
if (!socket.isAuthed) {
if (this.isAuthMessage(msg)) {
logger.info(`Attempting auth from ${ip} as ${msg.clientId}`);
await this.handleAuth(socket, msg, ip);
} else {
logger.warn(`Received message before auth from ${ip}: ${JSON.stringify(msg)}`);
await this.handleInvalidMessage(socket, ip);
}
return;
}
logger.debug(`Routing message from ${socket.clientId}: ${JSON.stringify(msg)}`);
await wsHandler.handle(socket, socket.clientId!, msg);
}
private isAuthMessage(msg: WSMessage): msg is AuthMessage {
return msg.type === 'auth';
}
private async handleAuth(socket: AuthenticatedSocket, msg: AuthMessage, ip: string) {
if (msg.secret === this.secret) {
socket.isAuthed = true;
socket.clientId = msg.clientId;
wsClientManager.add(msg.clientId, socket);
logger.info(`Auth success from ${ip}, clientId: ${msg.clientId}`);
await WsTools.send(socket, { type: 'auth', success: true });
} else {
logger.warn(`Auth failed from ${ip} (invalid secret), clientId: ${msg.clientId}`);
await WsTools.send(socket, { type: 'auth', success: false });
socket.close(4001, 'Authentication failed');
}
}
private handleDisconnect(socket: AuthenticatedSocket) {
if (socket.heartbeat) clearInterval(socket.heartbeat);
if (socket.clientId) {
wsClientManager.remove(socket.clientId);
logger.info(`Removed client ${socket.clientId} from manager`);
}
}
}
const wsServer = new WSServer();
export default wsServer;