import { getSocket, type TypedSocket } from '@/lib/socket' import { SERVER_URL } from '@/lib/config' import { storage } from '@/lib/storage' import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react' import { toast } from 'sonner' import { fetchCurrentProfile } from '@/lib/profileApi' import { SocketContext, type SocketContextValue } from '@/providers/socket-context' /** Persistent toast id so we can dismiss it on reconnect */ const DISCONNECT_TOAST_ID = 'socket-disconnect' export function SocketProvider({ children }: { children: ReactNode }) { const socketRef = useRef(getSocket()) const hasDisconnectedRef = useRef(false) const [isConnected, setIsConnected] = useState(socketRef.current.connected) useEffect(() => { const socket = socketRef.current let cancelled = false let reauthenticating = false const onConnect = () => { setIsConnected(true) toast.dismiss(DISCONNECT_TOAST_ID) if (hasDisconnectedRef.current) { toast.success('已重新连接', { id: 'socket-reconnect' }) } } const onDisconnect = () => { setIsConnected(false) hasDisconnectedRef.current = true toast.warning('连接已断开,正在重连…', { id: DISCONNECT_TOAST_ID, duration: Infinity, }) } const bootstrapIdentity = async (showError = true): Promise => { try { const res = await fetch(`${SERVER_URL}/api/auth/identity/bootstrap`, { method: 'POST', credentials: 'include', }) if (!res.ok) { storage.clearUserId() if (showError) toast.error('身份初始化失败,请刷新重试') return false } const userId = res.headers.get('X-Identity-UserId') ?? res.headers.get('x-identity-userid') if (userId && userId.trim().length > 0) { await fetchCurrentProfile().catch(() => null) } else { storage.clearUserId() } return true } catch { storage.clearUserId() if (showError) toast.error('连接服务器失败,请稍后重试') return false } } const ensureIdentityAndConnect = async (showError = true): Promise => { const ok = await bootstrapIdentity(showError) if (ok && !cancelled && !socket.connected) { socket.connect() } } const onConnectError = async (err: Error) => { if (err.message !== 'UNAUTHENTICATED') return if (cancelled || reauthenticating) return reauthenticating = true try { const ok = await bootstrapIdentity(false) if (ok && !cancelled && !socket.connected) { socket.connect() } } finally { reauthenticating = false } } socket.on('connect', onConnect) socket.on('disconnect', onDisconnect) socket.on('connect_error', onConnectError) ensureIdentityAndConnect() return () => { cancelled = true socket.off('connect', onConnect) socket.off('disconnect', onDisconnect) socket.off('connect_error', onConnectError) } }, []) const value = useMemo(() => ({ socket: socketRef.current, isConnected }), [isConnected]) return {children} }