feat: manage user online status and typing indicator in socket gateway

- Added tracking of online users with real-time status updates (online/offline).
- Implemented `handleTyping` to broadcast user typing events to recipients.
- Added `check_status` handler to query user online status.
- Enhanced CORS configuration to support multi-domain deployments with credentials.
This commit is contained in:
Mathis HERRIOT
2026-01-29 16:56:36 +01:00
parent 62bf03d07a
commit 9e9b1db012

View File

@@ -17,8 +17,16 @@ import { JwtService } from "../crypto/services/jwt.service";
@WebSocketGateway({
cors: {
origin: "*",
origin: (
_origin: string,
callback: (err: Error | null, allow?: boolean) => void,
) => {
// En production, on pourrait restreindre ici
// Pour l'instant on autorise tout en mode credentials pour faciliter le déploiement multi-domaines
callback(null, true);
},
credentials: true,
methods: ["GET", "POST"],
},
})
export class EventsGateway
@@ -28,6 +36,7 @@ export class EventsGateway
server!: Server;
private readonly logger = new Logger(EventsGateway.name);
private readonly onlineUsers = new Map<string, Set<string>>(); // userId -> Set of socketIds
constructor(
private readonly jwtService: JwtService,
@@ -69,6 +78,13 @@ export class EventsGateway
// Rejoindre une room personnelle pour les notifications
client.join(`user:${payload.sub}`);
// Gérer le statut en ligne
if (!this.onlineUsers.has(payload.sub)) {
this.onlineUsers.set(payload.sub, new Set());
this.server.emit("user_status", { userId: payload.sub, status: "online" });
}
this.onlineUsers.get(payload.sub)?.add(client.id);
this.logger.log(`Client connected: ${client.id} (User: ${payload.sub})`);
} catch (error) {
this.logger.error(`Connection error for client ${client.id}: ${error}`);
@@ -77,6 +93,15 @@ export class EventsGateway
}
handleDisconnect(client: Socket) {
const userId = client.data.user?.sub;
if (userId && this.onlineUsers.has(userId)) {
const sockets = this.onlineUsers.get(userId);
sockets?.delete(client.id);
if (sockets?.size === 0) {
this.onlineUsers.delete(userId);
this.server.emit("user_status", { userId, status: "offline" });
}
}
this.logger.log(`Client disconnected: ${client.id}`);
}
@@ -98,6 +123,31 @@ export class EventsGateway
this.logger.log(`Client ${client.id} left content room: ${contentId}`);
}
@SubscribeMessage("typing")
handleTyping(
@ConnectedSocket() client: Socket,
@MessageBody() data: { recipientId: string; isTyping: boolean },
) {
const userId = client.data.user?.sub;
if (!userId) return;
this.server.to(`user:${data.recipientId}`).emit("user_typing", {
userId,
isTyping: data.isTyping,
});
}
@SubscribeMessage("check_status")
handleCheckStatus(
@ConnectedSocket() _client: Socket,
@MessageBody() userId: string,
) {
return {
userId,
status: this.onlineUsers.has(userId) ? "online" : "offline",
};
}
// Méthode utilitaire pour envoyer des messages à un utilisateur spécifique
sendToUser(userId: string, event: string, data: any) {
this.server.to(`user:${userId}`).emit(event, data);