import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; import { OnGatewayConnection, WebSocketGateway, WebSocketServer, } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; type SocketWithUser = Socket & { data: { userId?: string } }; @WebSocketGateway({ cors: { origin: '*' }, namespace: 'notifications' }) export class NotificationsGateway implements OnGatewayConnection { @WebSocketServer() server!: Server; constructor( private readonly jwtService: JwtService, private readonly configService: ConfigService, ) {} async handleConnection(client: SocketWithUser) { const token = this.extractToken(client); if (!token) { client.disconnect(true); return; } try { const payload = this.jwtService.verify<{ sub: string; tokenType: string }>(token, { secret: this.configService.get('jwt.accessSecret', { infer: true }), }); if (payload.tokenType !== 'access') { client.disconnect(true); return; } client.data.userId = payload.sub; await client.join(this.userRoom(payload.sub)); } catch { client.disconnect(true); } } emitCreated(recipientId: string, notification: unknown, unreadCount: number): void { this.server.to(this.userRoom(recipientId)).emit('notification_created', notification); this.server.to(this.userRoom(recipientId)).emit('notifications_unread_count', { unreadCount }); } emitUnreadCount(recipientId: string, unreadCount: number): void { this.server.to(this.userRoom(recipientId)).emit('notifications_unread_count', { unreadCount }); } private extractToken(client: Socket): string | null { const authToken = client.handshake.auth?.token; if (typeof authToken === 'string' && authToken.trim()) { return authToken.replace(/^Bearer\s+/i, '').trim(); } const headerAuth = client.handshake.headers.authorization; if (typeof headerAuth === 'string' && headerAuth.trim()) { return headerAuth.replace(/^Bearer\s+/i, '').trim(); } return null; } private userRoom(userId: string): string { return `user:${userId}`; } }