Événements WebSocket
Événements émis par le client
join_conversation
Rejoindre une room de conversation pour recevoir les messages en temps réel.
socket.emit('join_conversation', conversationId: number)
// Côté serveur
socket.on('join_conversation', (conversationId) => {
socket.join(`conversation:${conversationId}`)
})
leave_conversation
Quitter une room.
socket.emit('leave_conversation', conversationId: number)
send_message
Envoyer un message dans une conversation.
socket.emit('send_message', {
conversation_id: number,
content: string, // max 2000 caractères
attachment?: string, // base64 data URL (image uniquement, max 10 Mo)
})
Validations côté serveur :
content: 1 à 2000 caractères (obligatoire)attachment: doit commencer pardata:image/(PNG, JPG, GIF, WebP)- Taille attachment : max 10 Mo
- L'expéditeur doit être participant de la conversation
// Côté serveur (résumé)
socket.on('send_message', async ({ conversation_id, content, attachment }) => {
// Vérification de la participation
const conversation = await Conversation.findByPk(conversation_id)
const isParticipant =
conversation.voyager_id === socket.userId ||
conversation.local_id === socket.userId
if (!isParticipant) return socket.emit('error', 'Non autorisé')
// Validation du contenu
if (!content || content.length > 2000) return socket.emit('error', 'Message invalide')
// Sauvegarde en DB
const message = await Message.create({
user_id: socket.userId,
conversation_id,
content,
attachment: attachment || null,
})
// Broadcast à la room
io.to(`conversation:${conversation_id}`).emit('new_message', {
...message.toJSON(),
Sender: { id: socket.userId, name: socket.dbUser.name },
})
})
typing
Indicateur de saisie en cours.
socket.emit('typing', {
conversation_id: number,
isTyping: boolean,
})
message_read
Marquer un message comme lu.
socket.emit('message_read', {
message_id: number,
conversation_id: number,
})
Événements émis par le serveur
new_message
Nouveau message reçu dans une conversation.
// Payload
{
id: number,
user_id: number,
conversation_id: number,
content: string,
attachment: string | null,
read: false,
createdAt: string,
Sender: { id: number, name: string }
}
Émis à tous les membres de la room (io.to(room).emit()).
user_typing
Un utilisateur est en train d'écrire.
// Payload
{
user_id: number,
conversation_id: number,
isTyping: boolean,
}
Émis à tous les membres de la room sauf l'expéditeur (socket.to(room).emit()).
message_read_update
Confirmation de lecture d'un message.
// Payload
{
message_id: number,
conversation_id: number,
read_by: number, // user_id du lecteur
}
error
Erreur lors du traitement d'un événement.
// Payload
{
message: string
}
Exemple complet (client)
// app/composables/useSocket.ts
const socket = connect()
// Rejoindre une conversation
socket.emit('join_conversation', 5)
// Envoyer un message
socket.emit('send_message', {
conversation_id: 5,
content: 'Salut ! Disponible samedi ?',
})
// Écouter les nouveaux messages
socket.on('new_message', (message) => {
messages.value.push(message)
})
// Écouter les indicateurs de frappe
socket.on('user_typing', ({ user_id, isTyping }) => {
typingUsers.value = isTyping
? [...typingUsers.value, user_id]
: typingUsers.value.filter(id => id !== user_id)
})