114 lines
3.9 KiB
TypeScript
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');
|
|
}
|
|
}
|
|
}
|