Files
negareh-api/src/modules/notification/notifications.gateway.ts
T
2026-01-14 11:29:24 +03:30

114 lines
3.9 KiB
TypeScript

import {
WebSocketGateway,
WebSocketServer,
// SubscribeMessage,
OnGatewayConnection,
OnGatewayDisconnect,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { Logger, Inject, forwardRef } from '@nestjs/common';
import { NotificationService } from './services/notification.service';
import { WsAdminAuthGuard, type AuthenticatedSocket } from './guards/ws-admin-auth.guard';
import { ModuleRef } from '@nestjs/core';
import { ExecutionContext } from '@nestjs/common';
import { IInAppNotificationPayload } from './interfaces/notification.interface';
@WebSocketGateway({
cors: {
origin: true,
credentials: true,
},
namespace: '/notifications',
transports: ['websocket', 'polling'],
})
export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server!: Server;
private readonly logger = new Logger(NotificationsGateway.name);
private readonly pingIntervals = new Map<string, NodeJS.Timeout>();
constructor(
@Inject(forwardRef(() => NotificationService))
private readonly notificationService: NotificationService,
private readonly moduleRef: ModuleRef,
) { }
async handleConnection(client: Socket) {
try {
await this.authenticateConnection(client);
const authenticatedClient = client as AuthenticatedSocket;
this.logger.log(`Admin connected: ${authenticatedClient.adminId}`);
this.handleJoinRoom(authenticatedClient);
// Start ping interval for this client
// const intervalId = setInterval(() => {
// void authenticatedClient.emit('ping', { message: 'ping', timestamp: new Date().toISOString() });
// }, 2000);
// this.pingIntervals.set(client.id, intervalId);
} catch (error) {
this.logger.error(
`Connection authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
);
void client.emit('error', { message: 'Authentication failed' });
client.disconnect();
}
}
handleDisconnect(client: Socket) {
this.logger.log(`Client disconnected: ${client.id}`);
this.handleLeaveRoom(client);
// Clear ping interval for this client
// const intervalId = this.pingIntervals.get(client.id);
// if (intervalId) {
// clearInterval(intervalId);
// this.pingIntervals.delete(client.id);
// }
}
sendInAppNotification(repipient: { adminId: string; }, payload: IInAppNotificationPayload) {
const room = this.getRoom(repipient.adminId,);
this.logger.log(`Sending in app notification to admin: ${repipient.adminId}`);
this.server.to(room).emit('notifications', payload, async () => {
// 3. ACK (delivered)
// await this.notificationService.markAsDelivered(payload.notificationId);
});
this.logger.log(`In app notification sent to admin: ${repipient.adminId}`);
}
private getRoom(adminId: string,) {
return `admin:${adminId}`;
}
handleLeaveRoom(client: AuthenticatedSocket) {
const room = this.getRoom(client.adminId!,);
void client.leave(room);
this.logger.log(`Admin ${client.adminId} (Client ${client.id}) left room: ${room}`);
void client.emit('left', { room, message: 'Successfully Admin left room' });
}
handleJoinRoom(client: AuthenticatedSocket) {
const room = this.getRoom(client.adminId!,);
void client.join(room);
this.logger.log(`Admin ${client.adminId} (Client ${client.id}) joined room: ${room}`);
void client.emit('joined', { room, message: 'Successfully Admin joined room' });
}
private async authenticateConnection(client: Socket): Promise<void> {
const guard = this.moduleRef.get(WsAdminAuthGuard);
const context = {
switchToWs: () => ({ getClient: () => client }),
getClass: () => NotificationsGateway,
getHandler: () => this.handleConnection,
} as unknown as ExecutionContext;
const canActivate = await guard.canActivate(context);
if (!canActivate) {
throw new Error('Authentication failed');
}
}
}