'use client'; import { useCallback, useEffect, useRef, useState } from 'react'; export interface UserStatus { user_id: number; is_online: boolean; last_activity: string | null; } // Глобальное хранилище статусов const userStatuses = new Map(); const listeners = new Set<(s: UserStatus) => void>(); export const subscribeToUserStatus = (cb: (s: UserStatus) => void) => { listeners.add(cb); return () => listeners.delete(cb); }; export const getUserStatus = (userId: number): UserStatus | null => userStatuses.get(userId) || null; const updateUserStatus = (s: UserStatus) => { userStatuses.set(s.user_id, s); listeners.forEach((cb) => { try { cb(s); } catch { // ignore } }); }; export const usePresenceWebSocket = (options?: { enabled?: boolean }) => { const enabled = options?.enabled ?? true; const [isConnected, setIsConnected] = useState(false); const wsRef = useRef(null); const pingRef = useRef(null); const connect = useCallback(() => { if (!enabled) return; const token = typeof window !== 'undefined' ? localStorage.getItem('access_token') : null; if (!token) return; let apiUrl = process.env.NEXT_PUBLIC_API_URL || window.location.origin; apiUrl = apiUrl.replace(/\/api\/?$/, '').replace(/\/api\//, '/'); apiUrl = apiUrl.replace(/\/$/, ''); const wsProtocol = apiUrl.startsWith('https') ? 'wss:' : 'ws:'; const wsHost = apiUrl.replace(/^https?:\/\//, ''); const wsUrl = `${wsProtocol}//${wsHost}/ws/presence/?token=${token}`; const ws = new WebSocket(wsUrl); wsRef.current = ws; ws.onopen = () => { setIsConnected(true); // ping каждые 30 секунд pingRef.current = window.setInterval(() => { try { if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'ping' })); } catch { // ignore } }, 30000); }; ws.onclose = () => { setIsConnected(false); if (pingRef.current) window.clearInterval(pingRef.current); pingRef.current = null; }; ws.onerror = () => { setIsConnected(false); }; ws.onmessage = (ev) => { try { const data = JSON.parse(ev.data); if (data?.type === 'user_status_update' && typeof data.user_id === 'number') { updateUserStatus({ user_id: data.user_id, is_online: !!data.is_online, last_activity: data.last_activity ?? null, }); } } catch { // ignore } }; }, [enabled]); const disconnect = useCallback(() => { if (pingRef.current) window.clearInterval(pingRef.current); pingRef.current = null; if (wsRef.current) { wsRef.current.close(); wsRef.current = null; } setIsConnected(false); }, []); useEffect(() => { disconnect(); connect(); return () => disconnect(); }, [connect, disconnect]); return { isConnected }; };