Files
iistwin/client/src/hooks/useEvents.ts

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),
};
};