feat: integrate user preferences for online status in WebSocket gateway
- Added `UsersService` to manage user preferences in `EventsGateway`. - Enhanced online/offline broadcasting to respect user `showOnlineStatus` preference. - Updated `handleTyping` and `check_status` to verify user preferences before emitting events. - Abstracted status broadcasting logic into `broadcastStatus`.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { ConfigService } from "@nestjs/config";
|
import { ConfigService } from "@nestjs/config";
|
||||||
import { Test, TestingModule } from "@nestjs/testing";
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
import { JwtService } from "../crypto/services/jwt.service";
|
import { JwtService } from "../crypto/services/jwt.service";
|
||||||
|
import { UsersService } from "../users/users.service";
|
||||||
import { EventsGateway } from "./events.gateway";
|
import { EventsGateway } from "./events.gateway";
|
||||||
|
|
||||||
describe("EventsGateway", () => {
|
describe("EventsGateway", () => {
|
||||||
@@ -15,12 +16,17 @@ describe("EventsGateway", () => {
|
|||||||
get: jest.fn().mockReturnValue("secret-password-32-chars-long-!!!"),
|
get: jest.fn().mockReturnValue("secret-password-32-chars-long-!!!"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockUsersService = {
|
||||||
|
findOne: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
EventsGateway,
|
EventsGateway,
|
||||||
{ provide: JwtService, useValue: mockJwtService },
|
{ provide: JwtService, useValue: mockJwtService },
|
||||||
{ provide: ConfigService, useValue: mockConfigService },
|
{ provide: ConfigService, useValue: mockConfigService },
|
||||||
|
{ provide: UsersService, useValue: mockUsersService },
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Logger } from "@nestjs/common";
|
import { forwardRef, Inject, Logger } from "@nestjs/common";
|
||||||
import { ConfigService } from "@nestjs/config";
|
import { ConfigService } from "@nestjs/config";
|
||||||
import {
|
import {
|
||||||
ConnectedSocket,
|
ConnectedSocket,
|
||||||
@@ -14,6 +14,7 @@ import { getIronSession } from "iron-session";
|
|||||||
import { Server, Socket } from "socket.io";
|
import { Server, Socket } from "socket.io";
|
||||||
import { getSessionOptions, SessionData } from "../auth/session.config";
|
import { getSessionOptions, SessionData } from "../auth/session.config";
|
||||||
import { JwtService } from "../crypto/services/jwt.service";
|
import { JwtService } from "../crypto/services/jwt.service";
|
||||||
|
import { UsersService } from "../users/users.service";
|
||||||
|
|
||||||
@WebSocketGateway({
|
@WebSocketGateway({
|
||||||
transports: ["websocket"],
|
transports: ["websocket"],
|
||||||
@@ -63,6 +64,8 @@ export class EventsGateway
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly jwtService: JwtService,
|
private readonly jwtService: JwtService,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
|
@Inject(forwardRef(() => UsersService))
|
||||||
|
private readonly usersService: UsersService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
afterInit(_server: Server) {
|
afterInit(_server: Server) {
|
||||||
@@ -106,9 +109,15 @@ export class EventsGateway
|
|||||||
|
|
||||||
// Gérer le statut en ligne
|
// Gérer le statut en ligne
|
||||||
const userId = payload.sub as string;
|
const userId = payload.sub as string;
|
||||||
|
|
||||||
if (!this.onlineUsers.has(userId)) {
|
if (!this.onlineUsers.has(userId)) {
|
||||||
this.onlineUsers.set(userId, new Set());
|
this.onlineUsers.set(userId, new Set());
|
||||||
this.server.emit("user_status", { userId, status: "online" });
|
|
||||||
|
// Vérifier les préférences de l'utilisateur
|
||||||
|
const user = await this.usersService.findOne(userId);
|
||||||
|
if (user?.showOnlineStatus) {
|
||||||
|
this.broadcastStatus(userId, "online");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.onlineUsers.get(userId)?.add(client.id);
|
this.onlineUsers.get(userId)?.add(client.id);
|
||||||
|
|
||||||
@@ -119,19 +128,31 @@ export class EventsGateway
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDisconnect(client: Socket) {
|
async handleDisconnect(client: Socket) {
|
||||||
const userId = client.data.user?.sub;
|
const userId = client.data.user?.sub;
|
||||||
if (userId && this.onlineUsers.has(userId)) {
|
if (userId && this.onlineUsers.has(userId)) {
|
||||||
const sockets = this.onlineUsers.get(userId);
|
const sockets = this.onlineUsers.get(userId);
|
||||||
sockets?.delete(client.id);
|
sockets?.delete(client.id);
|
||||||
if (sockets?.size === 0) {
|
if (sockets?.size === 0) {
|
||||||
this.onlineUsers.delete(userId);
|
this.onlineUsers.delete(userId);
|
||||||
this.server.emit("user_status", { userId, status: "offline" });
|
|
||||||
|
const user = await this.usersService.findOne(userId);
|
||||||
|
if (user?.showOnlineStatus) {
|
||||||
|
this.broadcastStatus(userId, "offline");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.logger.log(`Client disconnected: ${client.id}`);
|
this.logger.log(`Client disconnected: ${client.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
broadcastStatus(userId: string, status: "online" | "offline") {
|
||||||
|
this.server.emit("user_status", { userId, status });
|
||||||
|
}
|
||||||
|
|
||||||
|
isUserOnline(userId: string): boolean {
|
||||||
|
return this.onlineUsers.has(userId);
|
||||||
|
}
|
||||||
|
|
||||||
@SubscribeMessage("join_content")
|
@SubscribeMessage("join_content")
|
||||||
handleJoinContent(
|
handleJoinContent(
|
||||||
@ConnectedSocket() client: Socket,
|
@ConnectedSocket() client: Socket,
|
||||||
@@ -151,13 +172,20 @@ export class EventsGateway
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeMessage("typing")
|
@SubscribeMessage("typing")
|
||||||
handleTyping(
|
async handleTyping(
|
||||||
@ConnectedSocket() client: Socket,
|
@ConnectedSocket() client: Socket,
|
||||||
@MessageBody() data: { recipientId: string; isTyping: boolean },
|
@MessageBody() data: { recipientId: string; isTyping: boolean },
|
||||||
) {
|
) {
|
||||||
const userId = client.data.user?.sub;
|
const userId = client.data.user?.sub;
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
|
// Optionnel: vérifier si l'utilisateur autorise le statut en ligne avant d'émettre "typing"
|
||||||
|
// ou si on considère que typing est une interaction directe qui outrepasse le statut.
|
||||||
|
// Instagram affiche "Typing..." même si le statut en ligne est désactivé si on est dans le chat.
|
||||||
|
// Mais par souci de cohérence avec "showOnlineStatus", on peut le vérifier.
|
||||||
|
const user = await this.usersService.findOne(userId);
|
||||||
|
if (!user?.showOnlineStatus) return;
|
||||||
|
|
||||||
this.server.to(`user:${data.recipientId}`).emit("user_typing", {
|
this.server.to(`user:${data.recipientId}`).emit("user_typing", {
|
||||||
userId,
|
userId,
|
||||||
isTyping: data.isTyping,
|
isTyping: data.isTyping,
|
||||||
@@ -165,13 +193,19 @@ export class EventsGateway
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeMessage("check_status")
|
@SubscribeMessage("check_status")
|
||||||
handleCheckStatus(
|
async handleCheckStatus(
|
||||||
@ConnectedSocket() _client: Socket,
|
@ConnectedSocket() _client: Socket,
|
||||||
@MessageBody() userId: string,
|
@MessageBody() userId: string,
|
||||||
) {
|
) {
|
||||||
|
const isOnline = this.onlineUsers.has(userId);
|
||||||
|
if (!isOnline) return { userId, status: "offline" };
|
||||||
|
|
||||||
|
const user = await this.usersService.findOne(userId);
|
||||||
|
if (!user?.showOnlineStatus) return { userId, status: "offline" };
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userId,
|
userId,
|
||||||
status: this.onlineUsers.has(userId) ? "online" : "offline",
|
status: "online",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user