Added detailed documentation files, including project overview, current status, specifications, implementation guide, and README structure. Organized content to improve navigation and streamline project understanding.
900 lines
24 KiB
Markdown
900 lines
24 KiB
Markdown
# Plan d'Implémentation des WebSockets
|
|
|
|
Ce document détaille le plan d'implémentation du système de communication en temps réel avec WebSockets pour l'application de création de groupes, basé sur les spécifications du cahier des charges.
|
|
|
|
## 1. Vue d'Ensemble
|
|
|
|
L'application utilisera Socket.IO pour la communication en temps réel entre les clients et le serveur. Cette approche permettra :
|
|
|
|
- La collaboration en temps réel entre les utilisateurs travaillant sur le même projet
|
|
- Les notifications instantanées pour les actions importantes
|
|
- La mise à jour automatique de l'interface utilisateur lorsque des changements sont effectués par d'autres utilisateurs
|
|
|
|
## 2. Architecture WebSocket
|
|
|
|
```mermaid
|
|
flowchart TB
|
|
subgraph Client["Clients"]
|
|
C1["Client 1"]
|
|
C2["Client 2"]
|
|
C3["Client 3"]
|
|
end
|
|
|
|
subgraph Server["Serveur NestJS"]
|
|
GW["WebSocket Gateway"]
|
|
AS["Authentication Service"]
|
|
PS["Project Service"]
|
|
GS["Group Service"]
|
|
NS["Notification Service"]
|
|
end
|
|
|
|
C1 <--> GW
|
|
C2 <--> GW
|
|
C3 <--> GW
|
|
|
|
GW <--> AS
|
|
GW <--> PS
|
|
GW <--> GS
|
|
GW <--> NS
|
|
```
|
|
|
|
## 3. Configuration de Socket.IO avec NestJS
|
|
|
|
### 3.1 Installation des Dépendances
|
|
|
|
```bash
|
|
pnpm add @nestjs/websockets @nestjs/platform-socket.io socket.io
|
|
pnpm add -D @types/socket.io
|
|
```
|
|
|
|
### 3.2 Module WebSockets
|
|
|
|
```typescript
|
|
// src/modules/websockets/websockets.module.ts
|
|
import { Module } from '@nestjs/common';
|
|
import { ProjectsGateway } from './gateways/projects.gateway';
|
|
import { GroupsGateway } from './gateways/groups.gateway';
|
|
import { NotificationsGateway } from './gateways/notifications.gateway';
|
|
import { WebSocketService } from './services/websocket.service';
|
|
import { AuthModule } from '../auth/auth.module';
|
|
import { ProjectsModule } from '../projects/projects.module';
|
|
import { GroupsModule } from '../groups/groups.module';
|
|
|
|
@Module({
|
|
imports: [AuthModule, ProjectsModule, GroupsModule],
|
|
providers: [
|
|
ProjectsGateway,
|
|
GroupsGateway,
|
|
NotificationsGateway,
|
|
WebSocketService,
|
|
],
|
|
exports: [WebSocketService],
|
|
})
|
|
export class WebSocketsModule {}
|
|
```
|
|
|
|
## 4. Service WebSocket
|
|
|
|
Le service WebSocket sera responsable de la gestion des connexions et des salles.
|
|
|
|
```typescript
|
|
// src/modules/websockets/services/websocket.service.ts
|
|
import { Injectable } from '@nestjs/common';
|
|
import { Server, Socket } from 'socket.io';
|
|
|
|
@Injectable()
|
|
export class WebSocketService {
|
|
private server: Server;
|
|
|
|
setServer(server: Server) {
|
|
this.server = server;
|
|
}
|
|
|
|
getServer(): Server {
|
|
return this.server;
|
|
}
|
|
|
|
joinRoom(socket: Socket, room: string) {
|
|
socket.join(room);
|
|
}
|
|
|
|
leaveRoom(socket: Socket, room: string) {
|
|
socket.leave(room);
|
|
}
|
|
|
|
emitToRoom(room: string, event: string, data: any) {
|
|
this.server.to(room).emit(event, data);
|
|
}
|
|
|
|
emitToAll(event: string, data: any) {
|
|
this.server.emit(event, data);
|
|
}
|
|
|
|
emitToUser(userId: string, event: string, data: any) {
|
|
this.emitToRoom(`user:${userId}`, event, data);
|
|
}
|
|
|
|
emitToProject(projectId: string, event: string, data: any) {
|
|
this.emitToRoom(`project:${projectId}`, event, data);
|
|
}
|
|
|
|
emitToGroup(groupId: string, event: string, data: any) {
|
|
this.emitToRoom(`group:${groupId}`, event, data);
|
|
}
|
|
}
|
|
```
|
|
|
|
## 5. Gateways WebSocket
|
|
|
|
### 5.1 Gateway de Base
|
|
|
|
```typescript
|
|
// src/modules/websockets/gateways/base.gateway.ts
|
|
import {
|
|
WebSocketGateway,
|
|
OnGatewayInit,
|
|
OnGatewayConnection,
|
|
OnGatewayDisconnect,
|
|
WebSocketServer,
|
|
} from '@nestjs/websockets';
|
|
import { Logger } from '@nestjs/common';
|
|
import { Server, Socket } from 'socket.io';
|
|
import { WebSocketService } from '../services/websocket.service';
|
|
import { AuthService } from '../../auth/services/auth.service';
|
|
|
|
@WebSocketGateway({
|
|
cors: {
|
|
origin: process.env.CORS_ORIGIN,
|
|
credentials: true,
|
|
},
|
|
})
|
|
export class BaseGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
|
|
@WebSocketServer() server: Server;
|
|
|
|
protected readonly logger = new Logger(this.constructor.name);
|
|
|
|
constructor(
|
|
protected readonly webSocketService: WebSocketService,
|
|
protected readonly authService: AuthService,
|
|
) {}
|
|
|
|
afterInit(server: Server) {
|
|
this.webSocketService.setServer(server);
|
|
this.logger.log('WebSocket Gateway initialized');
|
|
}
|
|
|
|
async handleConnection(client: Socket) {
|
|
try {
|
|
const token = client.handshake.auth.token;
|
|
|
|
if (!token) {
|
|
client.disconnect();
|
|
return;
|
|
}
|
|
|
|
const user = await this.authService.validateToken(token);
|
|
|
|
if (!user) {
|
|
client.disconnect();
|
|
return;
|
|
}
|
|
|
|
client.data.user = user;
|
|
|
|
// Rejoindre la salle personnelle de l'utilisateur
|
|
this.webSocketService.joinRoom(client, `user:${user.id}`);
|
|
|
|
this.logger.log(`Client connected: ${client.id}, User: ${user.id}`);
|
|
} catch (error) {
|
|
this.logger.error(`Connection error: ${error.message}`);
|
|
client.disconnect();
|
|
}
|
|
}
|
|
|
|
handleDisconnect(client: Socket) {
|
|
this.logger.log(`Client disconnected: ${client.id}`);
|
|
}
|
|
|
|
getUserFromSocket(client: Socket) {
|
|
return client.data.user;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5.2 Gateway Projets
|
|
|
|
```typescript
|
|
// src/modules/websockets/gateways/projects.gateway.ts
|
|
import { SubscribeMessage, MessageBody, ConnectedSocket } from '@nestjs/websockets';
|
|
import { Socket } from 'socket.io';
|
|
import { BaseGateway } from './base.gateway';
|
|
import { WebSocketService } from '../services/websocket.service';
|
|
import { AuthService } from '../../auth/services/auth.service';
|
|
import { ProjectsService } from '../../projects/services/projects.service';
|
|
|
|
export class ProjectsGateway extends BaseGateway {
|
|
constructor(
|
|
protected readonly webSocketService: WebSocketService,
|
|
protected readonly authService: AuthService,
|
|
private readonly projectsService: ProjectsService,
|
|
) {
|
|
super(webSocketService, authService);
|
|
}
|
|
|
|
@SubscribeMessage('project:join')
|
|
async handleJoinProject(
|
|
@ConnectedSocket() client: Socket,
|
|
@MessageBody() data: { projectId: string },
|
|
) {
|
|
try {
|
|
const user = this.getUserFromSocket(client);
|
|
const { projectId } = data;
|
|
|
|
// Vérifier si l'utilisateur a accès au projet
|
|
const hasAccess = await this.projectsService.hasAccess(projectId, user.id);
|
|
|
|
if (!hasAccess) {
|
|
return { error: 'Access denied' };
|
|
}
|
|
|
|
// Rejoindre la salle du projet
|
|
this.webSocketService.joinRoom(client, `project:${projectId}`);
|
|
|
|
this.logger.log(`User ${user.id} joined project ${projectId}`);
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
this.logger.error(`Error joining project: ${error.message}`);
|
|
return { error: 'Failed to join project' };
|
|
}
|
|
}
|
|
|
|
@SubscribeMessage('project:leave')
|
|
handleLeaveProject(
|
|
@ConnectedSocket() client: Socket,
|
|
@MessageBody() data: { projectId: string },
|
|
) {
|
|
try {
|
|
const user = this.getUserFromSocket(client);
|
|
const { projectId } = data;
|
|
|
|
// Quitter la salle du projet
|
|
this.webSocketService.leaveRoom(client, `project:${projectId}`);
|
|
|
|
this.logger.log(`User ${user.id} left project ${projectId}`);
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
this.logger.error(`Error leaving project: ${error.message}`);
|
|
return { error: 'Failed to leave project' };
|
|
}
|
|
}
|
|
|
|
@SubscribeMessage('project:update')
|
|
async handleUpdateProject(
|
|
@ConnectedSocket() client: Socket,
|
|
@MessageBody() data: { projectId: string, changes: any },
|
|
) {
|
|
try {
|
|
const user = this.getUserFromSocket(client);
|
|
const { projectId, changes } = data;
|
|
|
|
// Vérifier si l'utilisateur a accès au projet
|
|
const hasAccess = await this.projectsService.hasAccess(projectId, user.id);
|
|
|
|
if (!hasAccess) {
|
|
return { error: 'Access denied' };
|
|
}
|
|
|
|
// Émettre l'événement de mise à jour à tous les clients dans la salle du projet
|
|
this.webSocketService.emitToProject(projectId, 'project:updated', {
|
|
projectId,
|
|
changes,
|
|
updatedBy: user.id,
|
|
});
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
this.logger.error(`Error updating project: ${error.message}`);
|
|
return { error: 'Failed to update project' };
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5.3 Gateway Groupes
|
|
|
|
```typescript
|
|
// src/modules/websockets/gateways/groups.gateway.ts
|
|
import { SubscribeMessage, MessageBody, ConnectedSocket } from '@nestjs/websockets';
|
|
import { Socket } from 'socket.io';
|
|
import { BaseGateway } from './base.gateway';
|
|
import { WebSocketService } from '../services/websocket.service';
|
|
import { AuthService } from '../../auth/services/auth.service';
|
|
import { GroupsService } from '../../groups/services/groups.service';
|
|
import { ProjectsService } from '../../projects/services/projects.service';
|
|
|
|
export class GroupsGateway extends BaseGateway {
|
|
constructor(
|
|
protected readonly webSocketService: WebSocketService,
|
|
protected readonly authService: AuthService,
|
|
private readonly groupsService: GroupsService,
|
|
private readonly projectsService: ProjectsService,
|
|
) {
|
|
super(webSocketService, authService);
|
|
}
|
|
|
|
@SubscribeMessage('group:join')
|
|
async handleJoinGroup(
|
|
@ConnectedSocket() client: Socket,
|
|
@MessageBody() data: { groupId: string },
|
|
) {
|
|
try {
|
|
const user = this.getUserFromSocket(client);
|
|
const { groupId } = data;
|
|
|
|
// Récupérer le groupe et vérifier si l'utilisateur a accès au projet associé
|
|
const group = await this.groupsService.findById(groupId);
|
|
|
|
if (!group) {
|
|
return { error: 'Group not found' };
|
|
}
|
|
|
|
const hasAccess = await this.projectsService.hasAccess(group.projectId, user.id);
|
|
|
|
if (!hasAccess) {
|
|
return { error: 'Access denied' };
|
|
}
|
|
|
|
// Rejoindre la salle du groupe
|
|
this.webSocketService.joinRoom(client, `group:${groupId}`);
|
|
|
|
this.logger.log(`User ${user.id} joined group ${groupId}`);
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
this.logger.error(`Error joining group: ${error.message}`);
|
|
return { error: 'Failed to join group' };
|
|
}
|
|
}
|
|
|
|
@SubscribeMessage('group:leave')
|
|
handleLeaveGroup(
|
|
@ConnectedSocket() client: Socket,
|
|
@MessageBody() data: { groupId: string },
|
|
) {
|
|
try {
|
|
const user = this.getUserFromSocket(client);
|
|
const { groupId } = data;
|
|
|
|
// Quitter la salle du groupe
|
|
this.webSocketService.leaveRoom(client, `group:${groupId}`);
|
|
|
|
this.logger.log(`User ${user.id} left group ${groupId}`);
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
this.logger.error(`Error leaving group: ${error.message}`);
|
|
return { error: 'Failed to leave group' };
|
|
}
|
|
}
|
|
|
|
@SubscribeMessage('group:update')
|
|
async handleUpdateGroup(
|
|
@ConnectedSocket() client: Socket,
|
|
@MessageBody() data: { groupId: string, changes: any },
|
|
) {
|
|
try {
|
|
const user = this.getUserFromSocket(client);
|
|
const { groupId, changes } = data;
|
|
|
|
// Récupérer le groupe et vérifier si l'utilisateur a accès au projet associé
|
|
const group = await this.groupsService.findById(groupId);
|
|
|
|
if (!group) {
|
|
return { error: 'Group not found' };
|
|
}
|
|
|
|
const hasAccess = await this.projectsService.hasAccess(group.projectId, user.id);
|
|
|
|
if (!hasAccess) {
|
|
return { error: 'Access denied' };
|
|
}
|
|
|
|
// Émettre l'événement de mise à jour à tous les clients dans la salle du groupe
|
|
this.webSocketService.emitToGroup(groupId, 'group:updated', {
|
|
groupId,
|
|
changes,
|
|
updatedBy: user.id,
|
|
});
|
|
|
|
// Émettre également l'événement au niveau du projet
|
|
this.webSocketService.emitToProject(group.projectId, 'group:updated', {
|
|
groupId,
|
|
changes,
|
|
updatedBy: user.id,
|
|
});
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
this.logger.error(`Error updating group: ${error.message}`);
|
|
return { error: 'Failed to update group' };
|
|
}
|
|
}
|
|
|
|
@SubscribeMessage('group:addPerson')
|
|
async handleAddPersonToGroup(
|
|
@ConnectedSocket() client: Socket,
|
|
@MessageBody() data: { groupId: string, personId: string },
|
|
) {
|
|
try {
|
|
const user = this.getUserFromSocket(client);
|
|
const { groupId, personId } = data;
|
|
|
|
// Récupérer le groupe et vérifier si l'utilisateur a accès au projet associé
|
|
const group = await this.groupsService.findById(groupId);
|
|
|
|
if (!group) {
|
|
return { error: 'Group not found' };
|
|
}
|
|
|
|
const hasAccess = await this.projectsService.hasAccess(group.projectId, user.id);
|
|
|
|
if (!hasAccess) {
|
|
return { error: 'Access denied' };
|
|
}
|
|
|
|
// Émettre l'événement d'ajout de personne à tous les clients dans la salle du groupe
|
|
this.webSocketService.emitToGroup(groupId, 'group:personAdded', {
|
|
groupId,
|
|
personId,
|
|
addedBy: user.id,
|
|
});
|
|
|
|
// Émettre également l'événement au niveau du projet
|
|
this.webSocketService.emitToProject(group.projectId, 'group:personAdded', {
|
|
groupId,
|
|
personId,
|
|
addedBy: user.id,
|
|
});
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
this.logger.error(`Error adding person to group: ${error.message}`);
|
|
return { error: 'Failed to add person to group' };
|
|
}
|
|
}
|
|
|
|
@SubscribeMessage('group:removePerson')
|
|
async handleRemovePersonFromGroup(
|
|
@ConnectedSocket() client: Socket,
|
|
@MessageBody() data: { groupId: string, personId: string },
|
|
) {
|
|
try {
|
|
const user = this.getUserFromSocket(client);
|
|
const { groupId, personId } = data;
|
|
|
|
// Récupérer le groupe et vérifier si l'utilisateur a accès au projet associé
|
|
const group = await this.groupsService.findById(groupId);
|
|
|
|
if (!group) {
|
|
return { error: 'Group not found' };
|
|
}
|
|
|
|
const hasAccess = await this.projectsService.hasAccess(group.projectId, user.id);
|
|
|
|
if (!hasAccess) {
|
|
return { error: 'Access denied' };
|
|
}
|
|
|
|
// Émettre l'événement de suppression de personne à tous les clients dans la salle du groupe
|
|
this.webSocketService.emitToGroup(groupId, 'group:personRemoved', {
|
|
groupId,
|
|
personId,
|
|
removedBy: user.id,
|
|
});
|
|
|
|
// Émettre également l'événement au niveau du projet
|
|
this.webSocketService.emitToProject(group.projectId, 'group:personRemoved', {
|
|
groupId,
|
|
personId,
|
|
removedBy: user.id,
|
|
});
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
this.logger.error(`Error removing person from group: ${error.message}`);
|
|
return { error: 'Failed to remove person from group' };
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5.4 Gateway Notifications
|
|
|
|
```typescript
|
|
// src/modules/websockets/gateways/notifications.gateway.ts
|
|
import { SubscribeMessage, MessageBody, ConnectedSocket } from '@nestjs/websockets';
|
|
import { Socket } from 'socket.io';
|
|
import { BaseGateway } from './base.gateway';
|
|
import { WebSocketService } from '../services/websocket.service';
|
|
import { AuthService } from '../../auth/services/auth.service';
|
|
|
|
export class NotificationsGateway extends BaseGateway {
|
|
constructor(
|
|
protected readonly webSocketService: WebSocketService,
|
|
protected readonly authService: AuthService,
|
|
) {
|
|
super(webSocketService, authService);
|
|
}
|
|
|
|
@SubscribeMessage('notification:read')
|
|
handleReadNotification(
|
|
@ConnectedSocket() client: Socket,
|
|
@MessageBody() data: { notificationId: string },
|
|
) {
|
|
try {
|
|
const user = this.getUserFromSocket(client);
|
|
const { notificationId } = data;
|
|
|
|
// Logique pour marquer la notification comme lue
|
|
|
|
this.logger.log(`User ${user.id} read notification ${notificationId}`);
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
this.logger.error(`Error reading notification: ${error.message}`);
|
|
return { error: 'Failed to read notification' };
|
|
}
|
|
}
|
|
|
|
// Méthode pour envoyer une notification à un utilisateur spécifique
|
|
sendNotificationToUser(userId: string, notification: any) {
|
|
this.webSocketService.emitToUser(userId, 'notification:new', notification);
|
|
}
|
|
|
|
// Méthode pour envoyer une notification à tous les utilisateurs d'un projet
|
|
sendNotificationToProject(projectId: string, notification: any) {
|
|
this.webSocketService.emitToProject(projectId, 'notification:new', notification);
|
|
}
|
|
}
|
|
```
|
|
|
|
## 6. Intégration avec les Services Existants
|
|
|
|
### 6.1 Service Projets
|
|
|
|
```typescript
|
|
// src/modules/projects/services/projects.service.ts
|
|
import { Injectable, Inject } from '@nestjs/common';
|
|
import { WebSocketService } from '../../websockets/services/websocket.service';
|
|
|
|
@Injectable()
|
|
export class ProjectsService {
|
|
constructor(
|
|
// Autres injections...
|
|
private readonly webSocketService: WebSocketService,
|
|
) {}
|
|
|
|
// Méthodes existantes...
|
|
|
|
async update(id: string, data: any, userId: string) {
|
|
// Logique de mise à jour du projet
|
|
|
|
// Notification en temps réel
|
|
this.webSocketService.emitToProject(id, 'project:updated', {
|
|
projectId: id,
|
|
changes: data,
|
|
updatedBy: userId,
|
|
});
|
|
|
|
return updatedProject;
|
|
}
|
|
|
|
async addCollaborator(projectId: string, collaboratorId: string, role: string, userId: string) {
|
|
// Logique d'ajout de collaborateur
|
|
|
|
// Notification en temps réel
|
|
this.webSocketService.emitToProject(projectId, 'project:collaboratorAdded', {
|
|
projectId,
|
|
collaboratorId,
|
|
role,
|
|
addedBy: userId,
|
|
});
|
|
|
|
// Notification personnelle au collaborateur
|
|
this.webSocketService.emitToUser(collaboratorId, 'notification:new', {
|
|
type: 'PROJECT_INVITATION',
|
|
projectId,
|
|
invitedBy: userId,
|
|
role,
|
|
});
|
|
|
|
return result;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 6.2 Service Groupes
|
|
|
|
```typescript
|
|
// src/modules/groups/services/groups.service.ts
|
|
import { Injectable, Inject } from '@nestjs/common';
|
|
import { WebSocketService } from '../../websockets/services/websocket.service';
|
|
|
|
@Injectable()
|
|
export class GroupsService {
|
|
constructor(
|
|
// Autres injections...
|
|
private readonly webSocketService: WebSocketService,
|
|
) {}
|
|
|
|
// Méthodes existantes...
|
|
|
|
async create(data: any, userId: string) {
|
|
// Logique de création de groupe
|
|
|
|
// Notification en temps réel
|
|
this.webSocketService.emitToProject(data.projectId, 'group:created', {
|
|
groupId: createdGroup.id,
|
|
group: createdGroup,
|
|
createdBy: userId,
|
|
});
|
|
|
|
return createdGroup;
|
|
}
|
|
|
|
async addPerson(groupId: string, personId: string, userId: string) {
|
|
// Logique d'ajout de personne au groupe
|
|
|
|
const group = await this.findById(groupId);
|
|
|
|
// Notification en temps réel
|
|
this.webSocketService.emitToGroup(groupId, 'group:personAdded', {
|
|
groupId,
|
|
personId,
|
|
addedBy: userId,
|
|
});
|
|
|
|
this.webSocketService.emitToProject(group.projectId, 'group:personAdded', {
|
|
groupId,
|
|
personId,
|
|
addedBy: userId,
|
|
});
|
|
|
|
return result;
|
|
}
|
|
}
|
|
```
|
|
|
|
## 7. Intégration avec le Frontend
|
|
|
|
### 7.1 Configuration du Client Socket.IO
|
|
|
|
```typescript
|
|
// frontend/lib/socket.ts
|
|
import { io, Socket } from 'socket.io-client';
|
|
import { useEffect, useState } from 'react';
|
|
|
|
let socket: Socket | null = null;
|
|
|
|
export const initializeSocket = (token: string) => {
|
|
if (socket) {
|
|
socket.disconnect();
|
|
}
|
|
|
|
socket = io(process.env.NEXT_PUBLIC_API_URL, {
|
|
auth: { token },
|
|
withCredentials: true,
|
|
});
|
|
|
|
return socket;
|
|
};
|
|
|
|
export const getSocket = () => {
|
|
return socket;
|
|
};
|
|
|
|
export const disconnectSocket = () => {
|
|
if (socket) {
|
|
socket.disconnect();
|
|
socket = null;
|
|
}
|
|
};
|
|
|
|
export const useSocket = (token: string | null) => {
|
|
const [isConnected, setIsConnected] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!token) {
|
|
disconnectSocket();
|
|
setIsConnected(false);
|
|
return;
|
|
}
|
|
|
|
const socketInstance = initializeSocket(token);
|
|
|
|
socketInstance.on('connect', () => {
|
|
setIsConnected(true);
|
|
});
|
|
|
|
socketInstance.on('disconnect', () => {
|
|
setIsConnected(false);
|
|
});
|
|
|
|
return () => {
|
|
socketInstance.off('connect');
|
|
socketInstance.off('disconnect');
|
|
};
|
|
}, [token]);
|
|
|
|
return { socket, isConnected };
|
|
};
|
|
```
|
|
|
|
### 7.2 Hook pour les Projets
|
|
|
|
```typescript
|
|
// frontend/hooks/useProjectSocket.ts
|
|
import { useEffect, useState } from 'react';
|
|
import { getSocket } from '../lib/socket';
|
|
|
|
export const useProjectSocket = (projectId: string | null) => {
|
|
const [isJoined, setIsJoined] = useState(false);
|
|
const socket = getSocket();
|
|
|
|
useEffect(() => {
|
|
if (!socket || !projectId) {
|
|
setIsJoined(false);
|
|
return;
|
|
}
|
|
|
|
// Rejoindre la salle du projet
|
|
socket.emit('project:join', { projectId }, (response) => {
|
|
if (response.success) {
|
|
setIsJoined(true);
|
|
} else {
|
|
console.error('Failed to join project:', response.error);
|
|
}
|
|
});
|
|
|
|
// Quitter la salle du projet lors du démontage
|
|
return () => {
|
|
socket.emit('project:leave', { projectId });
|
|
setIsJoined(false);
|
|
};
|
|
}, [socket, projectId]);
|
|
|
|
const updateProject = (changes: any) => {
|
|
if (!socket || !projectId || !isJoined) {
|
|
return Promise.reject('Not connected to project');
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
socket.emit('project:update', { projectId, changes }, (response) => {
|
|
if (response.success) {
|
|
resolve(response);
|
|
} else {
|
|
reject(response.error);
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
return { isJoined, updateProject };
|
|
};
|
|
```
|
|
|
|
### 7.3 Hook pour les Groupes
|
|
|
|
```typescript
|
|
// frontend/hooks/useGroupSocket.ts
|
|
import { useEffect, useState } from 'react';
|
|
import { getSocket } from '../lib/socket';
|
|
|
|
export const useGroupSocket = (groupId: string | null) => {
|
|
const [isJoined, setIsJoined] = useState(false);
|
|
const socket = getSocket();
|
|
|
|
useEffect(() => {
|
|
if (!socket || !groupId) {
|
|
setIsJoined(false);
|
|
return;
|
|
}
|
|
|
|
// Rejoindre la salle du groupe
|
|
socket.emit('group:join', { groupId }, (response) => {
|
|
if (response.success) {
|
|
setIsJoined(true);
|
|
} else {
|
|
console.error('Failed to join group:', response.error);
|
|
}
|
|
});
|
|
|
|
// Quitter la salle du groupe lors du démontage
|
|
return () => {
|
|
socket.emit('group:leave', { groupId });
|
|
setIsJoined(false);
|
|
};
|
|
}, [socket, groupId]);
|
|
|
|
const updateGroup = (changes: any) => {
|
|
if (!socket || !groupId || !isJoined) {
|
|
return Promise.reject('Not connected to group');
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
socket.emit('group:update', { groupId, changes }, (response) => {
|
|
if (response.success) {
|
|
resolve(response);
|
|
} else {
|
|
reject(response.error);
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
const addPersonToGroup = (personId: string) => {
|
|
if (!socket || !groupId || !isJoined) {
|
|
return Promise.reject('Not connected to group');
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
socket.emit('group:addPerson', { groupId, personId }, (response) => {
|
|
if (response.success) {
|
|
resolve(response);
|
|
} else {
|
|
reject(response.error);
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
const removePersonFromGroup = (personId: string) => {
|
|
if (!socket || !groupId || !isJoined) {
|
|
return Promise.reject('Not connected to group');
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
socket.emit('group:removePerson', { groupId, personId }, (response) => {
|
|
if (response.success) {
|
|
resolve(response);
|
|
} else {
|
|
reject(response.error);
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
return { isJoined, updateGroup, addPersonToGroup, removePersonFromGroup };
|
|
};
|
|
```
|
|
|
|
### 7.4 Hook pour les Notifications
|
|
|
|
```typescript
|
|
// frontend/hooks/useNotifications.ts
|
|
import { useEffect, useState } from 'react';
|
|
import { getSocket } from '../lib/socket';
|
|
|
|
export const useNotifications = () => {
|
|
const [notifications, setNotifications] = useState([]);
|
|
const socket = getSocket();
|
|
|
|
useEffect(() => {
|
|
if (!socket) {
|
|
return;
|
|
}
|
|
|
|
// Écouter les nouvelles notifications
|
|
socket.on('notification:new', (notification) => {
|
|
setNotifications((prev) => [notification, ...prev]);
|
|
});
|
|
|
|
return () => {
|
|
socket.off('notification:new');
|
|
};
|
|
}, [socket]);
|
|
|
|
const markAsRead = (notificationId: string) => {
|
|
if (!socket) { |