workspace-v1/shared/libs/websocket/_reference/websocket.gateway.reference.ts
rckrdmrd 66161b1566 feat: Workspace-v1 complete migration with NEXUS v3.4
Sistema NEXUS v3.4 migrado con:

Estructura principal:
- core/orchestration: Sistema SIMCO + CAPVED (27 directivas, 28 perfiles)
- core/catalog: Catalogo de funcionalidades reutilizables
- shared/knowledge-base: Base de conocimiento compartida
- devtools/scripts: Herramientas de desarrollo
- control-plane/registries: Control de servicios y CI/CD
- orchestration/: Configuracion de orquestacion de agentes

Proyectos incluidos (11):
- gamilit (submodule -> GitHub)
- trading-platform (OrbiquanTIA)
- erp-suite con 5 verticales:
  - erp-core, construccion, vidrio-templado
  - mecanicas-diesel, retail, clinicas
- betting-analytics
- inmobiliaria-analytics
- platform_marketing_content
- pos-micro, erp-basico

Configuracion:
- .gitignore completo para Node.js/Python/Docker
- gamilit como submodule (git@github.com:rckrdmrd/gamilit-workspace.git)
- Sistema de puertos estandarizado (3005-3199)

Generated with NEXUS v3.4 Migration System
EPIC-010: Configuracion Git y Repositorios
2026-01-04 03:37:42 -06:00

199 lines
5.2 KiB
TypeScript

/**
* WEBSOCKET GATEWAY - REFERENCE IMPLEMENTATION
*
* @description Gateway WebSocket para comunicación en tiempo real.
* Soporta autenticación JWT, rooms y eventos tipados.
*
* @usage
* ```typescript
* // En el cliente
* const socket = io('http://localhost:3000', {
* auth: { token: 'jwt-token' }
* });
* socket.emit('join-room', { roomId: 'room-1' });
* socket.on('message', (data) => console.log(data));
* ```
*
* @origin gamilit (patrón base)
*/
import {
WebSocketGateway,
WebSocketServer,
SubscribeMessage,
OnGatewayConnection,
OnGatewayDisconnect,
ConnectedSocket,
MessageBody,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { Logger, UseGuards } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
// Adaptar imports según proyecto
// import { WsAuthGuard } from '../guards';
@WebSocketGateway({
cors: {
origin: process.env.CORS_ORIGIN || '*',
credentials: true,
},
namespace: '/ws',
})
export class AppWebSocketGateway implements OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server: Server;
private readonly logger = new Logger(AppWebSocketGateway.name);
private readonly connectedClients = new Map<string, ConnectedClient>();
constructor(private readonly jwtService: JwtService) {}
/**
* Manejar nueva conexión
*/
async handleConnection(client: Socket) {
try {
// Extraer y validar token
const token = client.handshake.auth?.token || client.handshake.headers?.authorization?.replace('Bearer ', '');
if (!token) {
this.disconnect(client, 'No token provided');
return;
}
const payload = this.jwtService.verify(token);
// Almacenar cliente conectado
this.connectedClients.set(client.id, {
socketId: client.id,
userId: payload.sub,
role: payload.role,
connectedAt: new Date(),
});
// Auto-join a room del usuario
client.join(`user:${payload.sub}`);
this.logger.log(`Client connected: ${client.id} (user: ${payload.sub})`);
client.emit('connected', { message: 'Successfully connected' });
} catch (error) {
this.disconnect(client, 'Invalid token');
}
}
/**
* Manejar desconexión
*/
handleDisconnect(client: Socket) {
const clientInfo = this.connectedClients.get(client.id);
this.connectedClients.delete(client.id);
this.logger.log(`Client disconnected: ${client.id} (user: ${clientInfo?.userId || 'unknown'})`);
}
/**
* Unirse a una sala
*/
@SubscribeMessage('join-room')
handleJoinRoom(
@ConnectedSocket() client: Socket,
@MessageBody() data: { roomId: string },
) {
client.join(data.roomId);
this.logger.debug(`Client ${client.id} joined room: ${data.roomId}`);
return { event: 'room-joined', data: { roomId: data.roomId } };
}
/**
* Salir de una sala
*/
@SubscribeMessage('leave-room')
handleLeaveRoom(
@ConnectedSocket() client: Socket,
@MessageBody() data: { roomId: string },
) {
client.leave(data.roomId);
this.logger.debug(`Client ${client.id} left room: ${data.roomId}`);
return { event: 'room-left', data: { roomId: data.roomId } };
}
/**
* Enviar mensaje a una sala
*/
@SubscribeMessage('message')
handleMessage(
@ConnectedSocket() client: Socket,
@MessageBody() data: { roomId: string; message: string },
) {
const clientInfo = this.connectedClients.get(client.id);
// Broadcast a la sala (excepto al sender)
client.to(data.roomId).emit('message', {
senderId: clientInfo?.userId,
message: data.message,
timestamp: new Date().toISOString(),
});
return { event: 'message-sent', data: { success: true } };
}
// ============ MÉTODOS PÚBLICOS PARA SERVICIOS ============
/**
* Enviar notificación a un usuario específico
*/
sendToUser(userId: string, event: string, data: any) {
this.server.to(`user:${userId}`).emit(event, data);
}
/**
* Enviar a una sala
*/
sendToRoom(roomId: string, event: string, data: any) {
this.server.to(roomId).emit(event, data);
}
/**
* Broadcast a todos los clientes conectados
*/
broadcast(event: string, data: any) {
this.server.emit(event, data);
}
/**
* Obtener clientes conectados en una sala
*/
async getClientsInRoom(roomId: string): Promise<string[]> {
const sockets = await this.server.in(roomId).fetchSockets();
return sockets.map(s => s.id);
}
/**
* Verificar si un usuario está conectado
*/
isUserConnected(userId: string): boolean {
for (const client of this.connectedClients.values()) {
if (client.userId === userId) return true;
}
return false;
}
// ============ HELPERS PRIVADOS ============
private disconnect(client: Socket, reason: string) {
client.emit('error', { message: reason });
client.disconnect(true);
this.logger.warn(`Client ${client.id} disconnected: ${reason}`);
}
}
// ============ TIPOS ============
interface ConnectedClient {
socketId: string;
userId: string;
role: string;
connectedAt: Date;
}