import { useCallback, useEffect, useRef, useState } from "react"; import { io } from "socket.io-client"; interface UseDraftSocketReturn { isConnected: boolean; connectionError: string | null; isReconnecting: boolean; reconnectCount: number; on: (event: string, callback: (...args: any[]) => void) => void; off: (event: string, callback?: (...args: any[]) => void) => void; emit: (event: string, ...args: any[]) => void; } export function useDraftSocket(seasonId: string, teamId?: string): UseDraftSocketReturn { const socketRef = useRef | null>(null); const hasConnectedOnce = useRef(false); const [isConnected, setIsConnected] = useState(false); const [connectionError, setConnectionError] = useState(null); const [isReconnecting, setIsReconnecting] = useState(false); const [reconnectCount, setReconnectCount] = useState(0); useEffect(() => { // Reset per-socket state so a new seasonId/teamId doesn't inherit the previous // socket's connect history and falsely treat its first connect as a reconnect. hasConnectedOnce.current = false; const socket = io({ path: "/socket.io", transports: ["websocket", "polling"], }); socketRef.current = socket; socket.on("connect", () => { console.log("Connected to Socket.IO:", socket.id); const isReconnect = hasConnectedOnce.current; hasConnectedOnce.current = true; setIsConnected(true); setConnectionError(null); setIsReconnecting(false); socket.emit("join-draft", seasonId, teamId); if (isReconnect) { setReconnectCount((c) => c + 1); } }); socket.on("disconnect", (reason) => { console.log("Disconnected from Socket.IO:", reason); setIsConnected(false); if (reason === "io server disconnect") { setConnectionError("Server disconnected. Please refresh the page."); } else { setIsReconnecting(true); } }); socket.on("connect_error", (error) => { console.error("Socket.IO connection error:", error); setConnectionError(error.message || "Failed to connect to draft server"); setIsReconnecting(false); }); socket.io.on("reconnect_attempt", () => { console.log("Attempting to reconnect..."); setIsReconnecting(true); setConnectionError(null); }); socket.io.on("reconnect_failed", () => { console.error("Reconnection failed"); setConnectionError("Failed to reconnect. Please refresh the page."); setIsReconnecting(false); }); const handleOffline = () => { // Mark as reconnecting immediately — the OS fires this before Socket.IO's // heartbeat would detect the drop (~15s later). Note: "offline" can fire // spuriously on mobile; handleOnline corrects the state if the socket is // still alive when the network returns. setIsConnected(false); setIsReconnecting(true); }; const handleOnline = () => { if (socketRef.current?.connected) { // Network blipped but the socket stayed alive (came back within ping timeout window). // The socket never disconnected so no "connect" event will fire — correct UI directly. setIsConnected(true); setIsReconnecting(false); } else { // Genuinely disconnected — skip backoff and reconnect immediately. socketRef.current?.connect(); } }; window.addEventListener("offline", handleOffline); window.addEventListener("online", handleOnline); return () => { window.removeEventListener("offline", handleOffline); window.removeEventListener("online", handleOnline); console.log("Leaving draft room:", seasonId); socket.emit("leave-draft", seasonId); socket.disconnect(); }; }, [seasonId, teamId]); const on = useCallback((event: string, callback: (...args: any[]) => void) => { socketRef.current?.on(event, callback); }, []); const off = useCallback((event: string, callback?: (...args: any[]) => void) => { socketRef.current?.off(event, callback); }, []); const emit = useCallback((event: string, ...args: any[]) => { socketRef.current?.emit(event, ...args); }, []); return { isConnected, connectionError, isReconnecting, reconnectCount, on, off, emit, }; }