UNPKG

@pulzar/core

Version:

Next-generation Node.js framework for ultra-fast web applications with zero-reflection DI, GraphQL, WebSockets, events, and edge runtime support

237 lines 7.41 kB
import { WebSocketAdapter } from "./adapters/ws"; export class WebSocketGateway { adapter; options; messageHandlers = new Map(); eventHandlers = new Map(); connections = new Map(); rooms = new Map(); constructor(options = {}) { this.adapter = options.adapter || new WebSocketAdapter(); this.options = { adapter: this.adapter, path: "/ws", maxConnections: 1000, heartbeatInterval: 30000, heartbeatTimeout: 5000, enableCompression: true, enableMetrics: true, ...options, }; this.setupAdapter(); } /** * Register a message handler */ onMessage(type, handler) { this.messageHandlers.set(type, handler); } /** * Register an event handler */ onEvent(event, handler) { this.eventHandlers.set(event, handler); } /** * Broadcast message to all connections */ async broadcast(message, filter) { const contexts = Array.from(this.connections.values()); const filteredContexts = filter ? contexts.filter(filter) : contexts; const promises = filteredContexts.map((context) => this.adapter.send(context.id, message)); await Promise.allSettled(promises); } /** * Send message to specific connection */ async send(connectionId, message) { await this.adapter.send(connectionId, message); } /** * Send message to room */ async sendToRoom(room, message) { const roomConnections = this.rooms.get(room); if (!roomConnections) { return; } const promises = Array.from(roomConnections).map((connectionId) => this.adapter.send(connectionId, message)); await Promise.allSettled(promises); } /** * Join a room */ async joinRoom(connectionId, room) { if (!this.rooms.has(room)) { this.rooms.set(room, new Set()); } this.rooms.get(room).add(connectionId); } /** * Leave a room */ async leaveRoom(connectionId, room) { const roomConnections = this.rooms.get(room); if (roomConnections) { roomConnections.delete(connectionId); if (roomConnections.size === 0) { this.rooms.delete(room); } } } /** * Get connection context */ getConnection(connectionId) { return this.connections.get(connectionId); } /** * Get all connections */ getConnections() { return Array.from(this.connections.values()); } /** * Get connections in a room */ getRoomConnections(room) { const roomConnections = this.rooms.get(room); if (!roomConnections) { return []; } return Array.from(roomConnections) .map((id) => this.connections.get(id)) .filter(Boolean); } /** * Disconnect a connection */ async disconnect(connectionId, reason) { await this.adapter.disconnect(connectionId, reason); } /** * Get gateway statistics */ async getStats() { const adapterStats = await this.adapter.getStats(); return { connections: this.connections.size, rooms: this.rooms.size, messagesSent: adapterStats.messagesSent, messagesReceived: adapterStats.messagesReceived, errors: adapterStats.errors, }; } /** * Start the gateway */ async start() { await this.adapter.start(); this.startHeartbeat(); } /** * Stop the gateway */ async stop() { await this.adapter.stop(); // Disconnect all connections const disconnectPromises = Array.from(this.connections.keys()).map((id) => this.adapter.disconnect(id, "Gateway shutting down")); await Promise.allSettled(disconnectPromises); this.connections.clear(); this.rooms.clear(); } /** * Setup adapter event handlers */ setupAdapter() { this.adapter.onConnection(async (context) => { this.connections.set(context.id, context); const handler = this.eventHandlers.get("connection"); if (handler) { try { await handler(context, { type: "connection", data: null }); } catch (error) { console.error("Error in connection handler:", error); } } }); this.adapter.onDisconnection(async (context, reason) => { this.connections.delete(context.id); // Remove from all rooms for (const [room, connections] of this.rooms.entries()) { connections.delete(context.id); if (connections.size === 0) { this.rooms.delete(room); } } const handler = this.eventHandlers.get("disconnection"); if (handler) { try { await handler(context, { type: "disconnection", data: reason }); } catch (error) { console.error("Error in disconnection handler:", error); } } }); this.adapter.onMessage(async (context, message) => { const handler = this.messageHandlers.get(message.type); if (handler) { try { await handler(context, message); } catch (error) { console.error("Error in message handler:", error); // Send error response await this.adapter.send(context.id, { type: "error", data: { message: "Internal server error", originalType: message.type, }, }); } } else { // Send error for unknown message type await this.adapter.send(context.id, { type: "error", data: { message: `Unknown message type: ${message.type}`, }, }); } }); this.adapter.onError(async (context, error) => { const handler = this.eventHandlers.get("error"); if (handler) { try { await handler(context, { type: "error", data: error }); } catch (handlerError) { console.error("Error in error handler:", handlerError); } } }); } /** * Start heartbeat mechanism */ startHeartbeat() { if (this.options.heartbeatInterval <= 0) { return; } setInterval(async () => { const pingMessage = { type: "ping", data: { timestamp: Date.now() }, }; await this.broadcast(pingMessage); }, this.options.heartbeatInterval); } } export function createWebSocketGateway(options) { return new WebSocketGateway(options); } //# sourceMappingURL=gateway.js.map