mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-04-04 04:01:27 +00:00
feat: mise en place de la messagerie [#17]
This commit is contained in:
249
Front-End/src/hooks/useWebSocket.js
Normal file
249
Front-End/src/hooks/useWebSocket.js
Normal file
@ -0,0 +1,249 @@
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { WS_CHAT_URL } from '@/utils/Url';
|
||||
import logger from '@/utils/logger';
|
||||
|
||||
const useWebSocket = (userId, onMessage, onConnectionChange) => {
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [connectionStatus, setConnectionStatus] = useState('disconnected');
|
||||
const wsRef = useRef(null);
|
||||
const reconnectTimeoutRef = useRef(null);
|
||||
const reconnectAttemptsRef = useRef(0);
|
||||
const isConnectingRef = useRef(false); // Empêcher les connexions multiples
|
||||
const maxReconnectAttempts = 5;
|
||||
|
||||
// Récupération du token JWT
|
||||
const { data: session } = useSession();
|
||||
const authToken = session?.user?.token;
|
||||
|
||||
// Références stables pour les callbacks
|
||||
const onMessageRef = useRef(onMessage);
|
||||
const onConnectionChangeRef = useRef(onConnectionChange);
|
||||
|
||||
useEffect(() => {
|
||||
onMessageRef.current = onMessage;
|
||||
}, [onMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
onConnectionChangeRef.current = onConnectionChange;
|
||||
}, [onConnectionChange]);
|
||||
|
||||
const connect = useCallback(() => {
|
||||
if (!userId || !authToken) {
|
||||
logger.warn('WebSocket: userId ou token manquant');
|
||||
return;
|
||||
}
|
||||
|
||||
// Empêcher les connexions multiples simultanées
|
||||
if (
|
||||
isConnectingRef.current ||
|
||||
(wsRef.current && wsRef.current.readyState === WebSocket.CONNECTING)
|
||||
) {
|
||||
logger.debug('WebSocket: connexion déjà en cours');
|
||||
return;
|
||||
}
|
||||
|
||||
// Fermer la connexion existante si elle existe
|
||||
if (wsRef.current) {
|
||||
wsRef.current.close();
|
||||
wsRef.current = null;
|
||||
}
|
||||
|
||||
isConnectingRef.current = true;
|
||||
|
||||
try {
|
||||
// Ajouter le token à l'URL du WebSocket
|
||||
const wsUrl = new URL(WS_CHAT_URL(userId));
|
||||
wsUrl.searchParams.append('token', authToken);
|
||||
|
||||
wsRef.current = new WebSocket(wsUrl.toString());
|
||||
|
||||
wsRef.current.onopen = () => {
|
||||
logger.debug('WebSocket connecté');
|
||||
isConnectingRef.current = false;
|
||||
setIsConnected(true);
|
||||
setConnectionStatus('connected');
|
||||
reconnectAttemptsRef.current = 0;
|
||||
onConnectionChangeRef.current?.(true);
|
||||
};
|
||||
|
||||
wsRef.current.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
onMessageRef.current?.(data);
|
||||
} catch (error) {
|
||||
logger.error('Erreur lors du parsing du message WebSocket:', error);
|
||||
}
|
||||
};
|
||||
|
||||
wsRef.current.onclose = (event) => {
|
||||
logger.debug('WebSocket fermé:', event.code, event.reason);
|
||||
isConnectingRef.current = false;
|
||||
setIsConnected(false);
|
||||
setConnectionStatus('disconnected');
|
||||
onConnectionChangeRef.current?.(false);
|
||||
|
||||
// Tentative de reconnexion automatique seulement si la fermeture n'est pas intentionnelle
|
||||
if (
|
||||
event.code !== 1000 &&
|
||||
reconnectAttemptsRef.current < maxReconnectAttempts
|
||||
) {
|
||||
reconnectAttemptsRef.current++;
|
||||
setConnectionStatus('reconnecting');
|
||||
|
||||
const delay = Math.min(
|
||||
1000 * Math.pow(2, reconnectAttemptsRef.current),
|
||||
30000
|
||||
);
|
||||
reconnectTimeoutRef.current = setTimeout(() => {
|
||||
logger.debug(
|
||||
`Tentative de reconnexion ${reconnectAttemptsRef.current}/${maxReconnectAttempts}`
|
||||
);
|
||||
connect();
|
||||
}, delay);
|
||||
} else {
|
||||
setConnectionStatus('failed');
|
||||
}
|
||||
};
|
||||
|
||||
wsRef.current.onerror = (error) => {
|
||||
logger.error('Erreur WebSocket:', error);
|
||||
isConnectingRef.current = false;
|
||||
setConnectionStatus('error');
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Erreur lors de la création du WebSocket:', error);
|
||||
isConnectingRef.current = false;
|
||||
setConnectionStatus('error');
|
||||
}
|
||||
}, [userId, authToken]);
|
||||
|
||||
const disconnect = useCallback(() => {
|
||||
if (reconnectTimeoutRef.current) {
|
||||
clearTimeout(reconnectTimeoutRef.current);
|
||||
reconnectTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
if (wsRef.current) {
|
||||
wsRef.current.close();
|
||||
wsRef.current = null;
|
||||
}
|
||||
|
||||
setIsConnected(false);
|
||||
setConnectionStatus('disconnected');
|
||||
}, []);
|
||||
|
||||
const sendMessage = useCallback(
|
||||
(message) => {
|
||||
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
|
||||
// Ajouter le token à chaque message
|
||||
const messageWithAuth = {
|
||||
...message,
|
||||
token: authToken,
|
||||
};
|
||||
wsRef.current.send(JSON.stringify(messageWithAuth));
|
||||
return true;
|
||||
} else {
|
||||
logger.warn("WebSocket non connecté, impossible d'envoyer le message");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[authToken]
|
||||
);
|
||||
|
||||
const sendTypingStart = useCallback(
|
||||
(conversationId) => {
|
||||
sendMessage({
|
||||
type: 'typing_start',
|
||||
conversation_id: conversationId,
|
||||
});
|
||||
},
|
||||
[sendMessage]
|
||||
);
|
||||
|
||||
const sendTypingStop = useCallback(
|
||||
(conversationId) => {
|
||||
sendMessage({
|
||||
type: 'typing_stop',
|
||||
conversation_id: conversationId,
|
||||
});
|
||||
},
|
||||
[sendMessage]
|
||||
);
|
||||
|
||||
const markAsRead = useCallback(
|
||||
(conversationId) => {
|
||||
sendMessage({
|
||||
type: 'mark_as_read',
|
||||
conversation_id: conversationId,
|
||||
});
|
||||
},
|
||||
[sendMessage]
|
||||
);
|
||||
|
||||
const joinConversation = useCallback(
|
||||
(conversationId) => {
|
||||
sendMessage({
|
||||
type: 'join_conversation',
|
||||
conversation_id: conversationId,
|
||||
});
|
||||
},
|
||||
[sendMessage]
|
||||
);
|
||||
|
||||
const leaveConversation = useCallback(
|
||||
(conversationId) => {
|
||||
sendMessage({
|
||||
type: 'leave_conversation',
|
||||
conversation_id: conversationId,
|
||||
});
|
||||
},
|
||||
[sendMessage]
|
||||
);
|
||||
|
||||
const sendChatMessage = useCallback(
|
||||
(conversationId, content, attachment = null) => {
|
||||
const messageData = {
|
||||
type: 'send_message',
|
||||
conversation_id: conversationId,
|
||||
content: content,
|
||||
message_type: attachment ? 'file' : 'text',
|
||||
};
|
||||
|
||||
// Ajouter les informations du fichier si présent
|
||||
if (attachment) {
|
||||
messageData.attachment = attachment;
|
||||
}
|
||||
|
||||
return sendMessage(messageData);
|
||||
},
|
||||
[sendMessage]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Se connecter seulement si on a un userId et un token
|
||||
if (userId && authToken) {
|
||||
connect();
|
||||
}
|
||||
|
||||
return () => {
|
||||
disconnect();
|
||||
};
|
||||
}, [userId, authToken]); // Retirer connect et disconnect des dépendances
|
||||
|
||||
return {
|
||||
isConnected,
|
||||
connectionStatus,
|
||||
sendMessage,
|
||||
sendTypingStart,
|
||||
sendTypingStop,
|
||||
markAsRead,
|
||||
joinConversation,
|
||||
leaveConversation,
|
||||
sendChatMessage,
|
||||
reconnect: connect,
|
||||
disconnect,
|
||||
};
|
||||
};
|
||||
|
||||
export default useWebSocket;
|
||||
Reference in New Issue
Block a user