129 lines
3.6 KiB
TypeScript
129 lines
3.6 KiB
TypeScript
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<SocketConnection>,
|
|
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<boolean>((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<ClientToken[]> {
|
|
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)
|
|
);
|
|
}
|
|
}
|