Надёжная real-time доставка сообщений в чатах: SSE buffer, Last-Event-ID, polling fallback
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { useAuth } from './useAuth';
|
||||
import { sseManager } from '@/lib/sseManager';
|
||||
|
||||
@@ -21,6 +21,12 @@ export const useEvents = (options: UseEventsOptions = {}) => {
|
||||
const { user } = useAuth();
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [connectionId, setConnectionId] = useState<string | null>(null);
|
||||
// Храним актуальные callbacks в ref, чтобы слушатели SSE всегда вызывали
|
||||
// последние версии обработчиков, но не переподписывались на каждый рендер.
|
||||
const optionsRef = useRef(options);
|
||||
useEffect(() => {
|
||||
optionsRef.current = options;
|
||||
});
|
||||
|
||||
// Привязываем singleton к текущему пользователю
|
||||
useEffect(() => {
|
||||
@@ -35,22 +41,25 @@ export const useEvents = (options: UseEventsOptions = {}) => {
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Подписываемся на события — каждый useEvents добавляет свои callbacks к одному EventSource
|
||||
// Fixed: empty dependency array so we subscribe once on mount instead of
|
||||
// unsubscribing/resubscribing on every render (which dropped SSE events).
|
||||
// Подписываемся на события — каждый useEvents добавляет свои callbacks к одному EventSource.
|
||||
// Регистрируем обёртки один раз; обёртки читают актуальные callbacks из ref.
|
||||
useEffect(() => {
|
||||
const unsubs: Array<() => void> = [];
|
||||
if (options.onMessageRead) unsubs.push(sseManager.on('message_read', options.onMessageRead));
|
||||
if (options.onMessageCreated) unsubs.push(sseManager.on('message_created', options.onMessageCreated));
|
||||
if (options.onTaskUpdated) unsubs.push(sseManager.on('task_updated', options.onTaskUpdated));
|
||||
if (options.onNotification) unsubs.push(sseManager.on('notification', options.onNotification));
|
||||
if (options.onConvMessageCreated) unsubs.push(sseManager.on('conv_message_created', options.onConvMessageCreated));
|
||||
if (options.onConvMessageUpdated) unsubs.push(sseManager.on('conv_message_updated', options.onConvMessageUpdated));
|
||||
if (options.onConvMessageDeleted) unsubs.push(sseManager.on('conv_message_deleted', options.onConvMessageDeleted));
|
||||
if (options.onConvRead) unsubs.push(sseManager.on('conv_read', options.onConvRead));
|
||||
if (options.onConvCreated) unsubs.push(sseManager.on('conv_created', options.onConvCreated));
|
||||
if (options.onReactionUpdated) unsubs.push(sseManager.on('reaction_updated', options.onReactionUpdated));
|
||||
if (options.onPollUpdated) unsubs.push(sseManager.on('poll_updated', options.onPollUpdated));
|
||||
const wrap = <K extends keyof UseEventsOptions>(name: K) => (data: any) => {
|
||||
const cb = optionsRef.current[name] as any;
|
||||
if (cb) cb(data);
|
||||
};
|
||||
if (options.onMessageRead) unsubs.push(sseManager.on('message_read', wrap('onMessageRead')));
|
||||
if (options.onMessageCreated) unsubs.push(sseManager.on('message_created', wrap('onMessageCreated')));
|
||||
if (options.onTaskUpdated) unsubs.push(sseManager.on('task_updated', wrap('onTaskUpdated')));
|
||||
if (options.onNotification) unsubs.push(sseManager.on('notification', wrap('onNotification')));
|
||||
if (options.onConvMessageCreated) unsubs.push(sseManager.on('conv_message_created', wrap('onConvMessageCreated')));
|
||||
if (options.onConvMessageUpdated) unsubs.push(sseManager.on('conv_message_updated', wrap('onConvMessageUpdated')));
|
||||
if (options.onConvMessageDeleted) unsubs.push(sseManager.on('conv_message_deleted', wrap('onConvMessageDeleted')));
|
||||
if (options.onConvRead) unsubs.push(sseManager.on('conv_read', wrap('onConvRead')));
|
||||
if (options.onConvCreated) unsubs.push(sseManager.on('conv_created', wrap('onConvCreated')));
|
||||
if (options.onReactionUpdated) unsubs.push(sseManager.on('reaction_updated', wrap('onReactionUpdated')));
|
||||
if (options.onPollUpdated) unsubs.push(sseManager.on('poll_updated', wrap('onPollUpdated')));
|
||||
return () => { for (const u of unsubs) u(); };
|
||||
}, []);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user