import { HttpService } from '@nestjs/axios'; import { Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { firstValueFrom, retry } from 'rxjs'; import { Socket } from 'socket.io'; import { Repository } from 'typeorm'; import { ClientConnectionDto } from './dto/clientConnection.dto'; import { SocketConnection } from './socket.entity'; import { RedisService } from 'src/redis/redis.service'; export interface ClientToken { user_id: number; telephone_id: string; } @Injectable() export class SocketService { private readonly logger = new Logger(SocketService.name); constructor( @InjectRepository(SocketConnection) private socketConnectionRepository: Repository, private readonly httpService: HttpService, private readonly redisService: RedisService, ) {} private getRedisKey(token: ClientToken): string { return `online_clients:${token.user_id}-${token.telephone_id}`; } async clientConnection(data: ClientConnectionDto) { const client = this.socketConnectionRepository.create(data); const eventEndpoints = { connected: '/api/operator/online', disconnected: '/api/operator/offline', }; const endpoint = eventEndpoints[data.event]; if (endpoint) { const url = `${process.env.SERVICE_URL}${endpoint}?telephone_id=${client.telephone_id}`; try { await firstValueFrom( this.httpService.get(url, { timeout: 3000 }).pipe(retry(3)), ); } catch (error) { this.logger.error( `❌ Failed to notify ${data.event} for client ${client.telephone_id}:`, error.message || error, ); } } else { this.logger.warn(`⚠️ Unknown event type: ${data.event}`); } await this.socketConnectionRepository.save(client); } async sendMessageToClients( clients: Socket[], message: string, event: string, ): Promise<{ status: number; message: string }> { const promises = clients.map( (client) => new Promise((resolve) => { client.timeout(2000).emit(event, message, (err: any) => { resolve(!err); }); }), ); const results = await Promise.all(promises); const allSent = results.every((result) => result); if (!allSent) { return { status: 0, message: 'Failed to send to all clients!', }; } return { status: 1, message: 'Success in sending to all clients!', }; } getClientKey(token: ClientToken): string { return `${token.user_id}-${token.telephone_id}`; } async addOnlineClient(token: any, socketId: string) { const client = this.redisService.getClient(); const key = this.getRedisKey(token); await client.sAdd(key, socketId); } async removeOnlineClient(token: any, socketId: string) { const client = this.redisService.getClient(); const key = this.getRedisKey(token); await client.sRem(key, socketId); const count = await client.sCard(key); if (count === 0) { await client.del(key); } } async getAllOnlineClients(): Promise { const client = this.redisService.getClient(); const keys = await client.keys('online_clients:*'); return keys.map((key) => { const [user_id, telephone_id] = key.split(':')[1].split('-').map(Number); return { user_id, telephone_id }; }); } isValidToken(token: any): token is ClientToken { return ( token && typeof token.user_id === 'number' && typeof token.telephone_id === 'string' && !isNaN(token.user_id) && !isNaN(token.telephone_id) ); } }