83 lines
4.9 KiB
TypeScript
83 lines
4.9 KiB
TypeScript
import { useEffect, useState, useRef } from 'react';
|
|
import { useAuth } from './useAuth';
|
|
import { sseManager } from '@/lib/sseManager';
|
|
|
|
interface UseEventsOptions {
|
|
onMessageRead?: (data: { messageId: number; taskId: number; readerId: number; readerName: string; authorId: number }) => void;
|
|
onMessageCreated?: (data: { taskId: number; message: any }) => void;
|
|
onTaskUpdated?: (data: { taskId: number; formId: number; task: any }) => void;
|
|
onNotification?: (data: { type: string; taskId?: number; messageId?: number }) => void;
|
|
onConvMessageCreated?: (data: { conversationId: number; message: any }) => void;
|
|
onConvMessageUpdated?: (data: { conversationId: number; message: any }) => void;
|
|
onConvMessageDeleted?: (data: { conversationId: number; messageId: number }) => void;
|
|
onConvRead?: (data: { conversationId: number }) => void;
|
|
onConvCreated?: (data: any) => void;
|
|
onConvTypingStarted?: (data: { conversationId: number; botName: string }) => void;
|
|
onConvTypingStopped?: (data: { conversationId: number; botName: string }) => void;
|
|
onReactionUpdated?: (data: { taskMessageId?: number; convMessageId?: number; conversationId?: number; reactions: any[] }) => void;
|
|
onPollUpdated?: (data: { taskMessageId?: number; convMessageId?: number; conversationId?: number; poll: any }) => void;
|
|
/** GPS: объект получил новую позицию (payload формирует server/gps) */
|
|
onGpsAssetMoved?: (data: any) => void;
|
|
/** GPS: объект перешёл в офлайн по таймауту (payload формирует server/gps) */
|
|
onGpsAssetOffline?: (data: any) => void;
|
|
reconnectInterval?: number;
|
|
}
|
|
|
|
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(() => {
|
|
sseManager.setUser(user ? (user as any).id ?? null : null);
|
|
}, [user]);
|
|
|
|
// Подписываемся на состояние подключения
|
|
useEffect(() => {
|
|
return sseManager.onConnState((connected, connId) => {
|
|
setIsConnected(connected);
|
|
setConnectionId(connId);
|
|
});
|
|
}, []);
|
|
|
|
// Подписываемся на события — каждый useEvents добавляет свои callbacks к одному EventSource.
|
|
// Регистрируем обёртки один раз; обёртки читают актуальные callbacks из ref.
|
|
useEffect(() => {
|
|
const unsubs: Array<() => void> = [];
|
|
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.onConvTypingStarted) unsubs.push(sseManager.on('conv_typing_started', wrap('onConvTypingStarted')));
|
|
if (options.onConvTypingStopped) unsubs.push(sseManager.on('conv_typing_stopped', wrap('onConvTypingStopped')));
|
|
if (options.onReactionUpdated) unsubs.push(sseManager.on('reaction_updated', wrap('onReactionUpdated')));
|
|
if (options.onPollUpdated) unsubs.push(sseManager.on('poll_updated', wrap('onPollUpdated')));
|
|
if (options.onGpsAssetMoved) unsubs.push(sseManager.on('gps_asset_moved', wrap('onGpsAssetMoved')));
|
|
if (options.onGpsAssetOffline) unsubs.push(sseManager.on('gps_asset_offline', wrap('onGpsAssetOffline')));
|
|
return () => { for (const u of unsubs) u(); };
|
|
}, []);
|
|
|
|
return {
|
|
isConnected,
|
|
connectionId,
|
|
connect: () => sseManager.reconnect(),
|
|
disconnect: () => sseManager.setUser(null),
|
|
};
|
|
};
|