'use client'; import { createContext, useContext, useEffect, useState, useSyncExternalStore, type ReactNode, } from 'react'; import { io, type Socket } from 'socket.io-client'; import { useSession } from '@/lib/auth/client'; import { usePortStore } from '@/stores/ui-store'; const SocketContext = createContext(null); /** Returns true once the component has mounted on the client. Avoids calling * better-auth's `useSession()` (which dispatches React hooks via nanostores) * during the SSR pass — that combo throws "Invalid hook call" intermittently * in Next.js 15 dev mode. */ function useHasMounted(): boolean { return useSyncExternalStore( () => () => {}, () => true, () => false, ); } export function SocketProvider({ children }: { children: ReactNode }) { const hasMounted = useHasMounted(); return hasMounted ? ( {children} ) : ( {children} ); } function SocketProviderClient({ children }: { children: ReactNode }) { const { data: session } = useSession(); const currentPortId = usePortStore((s) => s.currentPortId); const [socket, setSocket] = useState(null); useEffect(() => { if (!session?.user || !currentPortId) return; const s = io(process.env.NEXT_PUBLIC_APP_URL!, { path: '/socket.io/', withCredentials: true, auth: { portId: currentPortId }, transports: ['websocket', 'polling'], }); s.on('connect', () => setSocket(s)); s.on('disconnect', () => setSocket(null)); return () => { s.disconnect(); setSocket(null); }; }, [session?.user, currentPortId]); return {children}; } export function useSocket() { return useContext(SocketContext); }