@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
JavaScript
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