import { WebSocketGateway as WSGateway, WebSocketServer, OnGatewayConnection, OnGatewayDisconnect, } from '@nestjs/websockets'; import { Logger } from '@nestjs/common'; import { Server, Socket } from 'socket.io'; interface AuthenticatedSocket extends Socket { data: { userId?: string; workspaceId?: string; }; } interface Task { id: string; workspaceId: string; [key: string]: unknown; } interface Event { id: string; workspaceId: string; [key: string]: unknown; } interface Project { id: string; workspaceId: string; [key: string]: unknown; } /** * WebSocket Gateway for real-time updates * Handles workspace-scoped rooms for broadcasting events */ @WSGateway({ cors: { origin: process.env.WEB_URL || 'http://localhost:3000', credentials: true, }, }) export class WebSocketGateway implements OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server!: Server; private readonly logger = new Logger(WebSocketGateway.name); /** * Handle client connection * Joins client to workspace-specific room */ async handleConnection(client: AuthenticatedSocket): Promise { const { userId, workspaceId } = client.data; if (!userId || !workspaceId) { this.logger.warn(`Client ${client.id} connected without authentication`); client.disconnect(); return; } const room = this.getWorkspaceRoom(workspaceId); await client.join(room); this.logger.log(`Client ${client.id} joined room ${room}`); } /** * Handle client disconnect * Leaves workspace room */ handleDisconnect(client: AuthenticatedSocket): void { const { workspaceId } = client.data; if (workspaceId) { const room = this.getWorkspaceRoom(workspaceId); client.leave(room); this.logger.log(`Client ${client.id} left room ${room}`); } } /** * Emit task:created event to workspace room */ emitTaskCreated(workspaceId: string, task: Task): void { const room = this.getWorkspaceRoom(workspaceId); this.server.to(room).emit('task:created', task); this.logger.debug(`Emitted task:created to ${room}`); } /** * Emit task:updated event to workspace room */ emitTaskUpdated(workspaceId: string, task: Task): void { const room = this.getWorkspaceRoom(workspaceId); this.server.to(room).emit('task:updated', task); this.logger.debug(`Emitted task:updated to ${room}`); } /** * Emit task:deleted event to workspace room */ emitTaskDeleted(workspaceId: string, taskId: string): void { const room = this.getWorkspaceRoom(workspaceId); this.server.to(room).emit('task:deleted', { id: taskId }); this.logger.debug(`Emitted task:deleted to ${room}`); } /** * Emit event:created event to workspace room */ emitEventCreated(workspaceId: string, event: Event): void { const room = this.getWorkspaceRoom(workspaceId); this.server.to(room).emit('event:created', event); this.logger.debug(`Emitted event:created to ${room}`); } /** * Emit event:updated event to workspace room */ emitEventUpdated(workspaceId: string, event: Event): void { const room = this.getWorkspaceRoom(workspaceId); this.server.to(room).emit('event:updated', event); this.logger.debug(`Emitted event:updated to ${room}`); } /** * Emit event:deleted event to workspace room */ emitEventDeleted(workspaceId: string, eventId: string): void { const room = this.getWorkspaceRoom(workspaceId); this.server.to(room).emit('event:deleted', { id: eventId }); this.logger.debug(`Emitted event:deleted to ${room}`); } /** * Emit project:created event to workspace room */ emitProjectCreated(workspaceId: string, project: Project): void { const room = this.getWorkspaceRoom(workspaceId); this.server.to(room).emit('project:created', project); this.logger.debug(`Emitted project:created to ${room}`); } /** * Emit project:updated event to workspace room */ emitProjectUpdated(workspaceId: string, project: Project): void { const room = this.getWorkspaceRoom(workspaceId); this.server.to(room).emit('project:updated', project); this.logger.debug(`Emitted project:updated to ${room}`); } /** * Emit project:deleted event to workspace room */ emitProjectDeleted(workspaceId: string, projectId: string): void { const room = this.getWorkspaceRoom(workspaceId); this.server.to(room).emit('project:deleted', { id: projectId }); this.logger.debug(`Emitted project:deleted to ${room}`); } /** * Emit cron:executed event when a scheduled command fires */ emitCronExecuted(workspaceId: string, data: { scheduleId: string; command: string; executedAt: Date }): void { const room = this.getWorkspaceRoom(workspaceId); this.server.to(room).emit('cron:executed', data); this.logger.debug(`Emitted cron:executed to ${room}`); } /** * Get workspace room name */ private getWorkspaceRoom(workspaceId: string): string { return `workspace:${workspaceId}`; } }