150 lines
4.1 KiB
TypeScript
150 lines
4.1 KiB
TypeScript
|
|
import { NextRequest } from 'next/server';
|
||
|
|
import { prisma } from '@/lib/prisma';
|
||
|
|
import { getCurrentUser } from '@/lib/auth';
|
||
|
|
|
||
|
|
// Server-Sent Events pour les notifications en temps réel
|
||
|
|
export async function GET(
|
||
|
|
request: NextRequest,
|
||
|
|
{ params }: { params: { id: string } }
|
||
|
|
) {
|
||
|
|
const user = await getCurrentUser();
|
||
|
|
if (!user) {
|
||
|
|
return new Response('Non autorisé', { status: 401 });
|
||
|
|
}
|
||
|
|
|
||
|
|
const conversation = await prisma.conversation.findUnique({
|
||
|
|
where: { id: params.id },
|
||
|
|
include: {
|
||
|
|
participants: true,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!conversation) {
|
||
|
|
return new Response('Conversation non trouvée', { status: 404 });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Vérifier que l'utilisateur est participant
|
||
|
|
const isParticipant = conversation.participants.some((p) => p.userId === user.id);
|
||
|
|
if (!isParticipant) {
|
||
|
|
return new Response('Accès non autorisé', { status: 403 });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Créer un stream SSE
|
||
|
|
const stream = new ReadableStream({
|
||
|
|
async start(controller) {
|
||
|
|
const encoder = new TextEncoder();
|
||
|
|
let isClosed = false;
|
||
|
|
|
||
|
|
// Fonction pour envoyer un événement
|
||
|
|
const sendEvent = (data: any) => {
|
||
|
|
if (isClosed) return;
|
||
|
|
try {
|
||
|
|
const message = `data: ${JSON.stringify(data)}\n\n`;
|
||
|
|
controller.enqueue(encoder.encode(message));
|
||
|
|
} catch (error) {
|
||
|
|
// Le controller est fermé, arrêter le polling
|
||
|
|
isClosed = true;
|
||
|
|
clearInterval(pollInterval);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Envoyer un événement de connexion
|
||
|
|
sendEvent({ type: 'connected', conversationId: params.id });
|
||
|
|
|
||
|
|
// Polling pour vérifier les nouveaux messages
|
||
|
|
let lastMessageId: string | null = null;
|
||
|
|
const pollInterval = setInterval(async () => {
|
||
|
|
if (isClosed) {
|
||
|
|
clearInterval(pollInterval);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
const lastMessage = await prisma.message.findFirst({
|
||
|
|
where: {
|
||
|
|
conversationId: params.id,
|
||
|
|
...(lastMessageId ? { id: { gt: lastMessageId } } : {}),
|
||
|
|
},
|
||
|
|
orderBy: {
|
||
|
|
createdAt: 'desc',
|
||
|
|
},
|
||
|
|
include: {
|
||
|
|
sender: {
|
||
|
|
select: {
|
||
|
|
id: true,
|
||
|
|
email: true,
|
||
|
|
name: true,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
files: true,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
if (lastMessage) {
|
||
|
|
lastMessageId = lastMessage.id;
|
||
|
|
sendEvent({
|
||
|
|
type: 'new_message',
|
||
|
|
message: lastMessage,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Vérifier les mises à jour de conversation
|
||
|
|
const updatedConversation = await prisma.conversation.findUnique({
|
||
|
|
where: { id: params.id },
|
||
|
|
include: {
|
||
|
|
participants: {
|
||
|
|
include: {
|
||
|
|
user: {
|
||
|
|
select: {
|
||
|
|
id: true,
|
||
|
|
email: true,
|
||
|
|
name: true,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
if (updatedConversation) {
|
||
|
|
sendEvent({
|
||
|
|
type: 'conversation_updated',
|
||
|
|
conversation: updatedConversation,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
// Si c'est une erreur de controller fermé, arrêter le polling
|
||
|
|
if (error instanceof Error && error.message.includes('closed')) {
|
||
|
|
isClosed = true;
|
||
|
|
clearInterval(pollInterval);
|
||
|
|
} else {
|
||
|
|
console.error('Erreur lors du polling:', error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, 1000); // Poll toutes les secondes
|
||
|
|
|
||
|
|
// Nettoyer lors de la fermeture
|
||
|
|
const cleanup = () => {
|
||
|
|
isClosed = true;
|
||
|
|
clearInterval(pollInterval);
|
||
|
|
try {
|
||
|
|
controller.close();
|
||
|
|
} catch (e) {
|
||
|
|
// Ignorer les erreurs de fermeture
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Gérer la fermeture de la connexion
|
||
|
|
request.signal?.addEventListener('abort', cleanup);
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
return new Response(stream, {
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'text/event-stream',
|
||
|
|
'Cache-Control': 'no-cache',
|
||
|
|
'Connection': 'keep-alive',
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}
|