"use client"; import { createContext, useContext, useEffect, useState, useRef, useCallback, type ReactNode, } from "react"; import { io, Socket } from "socket.io-client"; import { useAuth } from "./AuthContext"; import { API_BASE_URL } from "@/lib/api"; // Shape of a chat message coming from the server export interface ChatMessage { _id?: string; roomId: string; senderId: string; username: string; text: string; createdAt?: string; } // System messages related to a room (join/leave/info) export interface RoomNotice { roomId: string; message: string; } // Everything that the socket context will provide to the app interface SocketContextValue { socket: Socket | null; isConnected: boolean; mySocketId: string | null; // join / leave / send chat messages joinRoom: (roomId: string) => void; leaveRoom: (roomId: string) => void; sendMessage: (roomId: string, text: string) => void; // chat state stored globally messages: ChatMessage[]; notices: RoomNotice[]; currentRoom: string | null; setCurrentRoom: (roomId: string | null) => void; clearMessages: () => void; } //context // Create a global socket context const SocketContext = createContext(undefined); /* Provider */ export function SocketProvider({ children }: { children: ReactNode }) { const { user } = useAuth(); // Keep socket instance in a ref so it persists without rerenders const socketRef = useRef(null); // Connection status const [isConnected, setIsConnected] = useState(false); // Store current socket id assigned by server const [mySocketId, setMySocketId] = useState(null); // All chat messages received const [messages, setMessages] = useState([]); // System notices (join/leave/info messages) const [notices, setNotices] = useState([]); // Currently active chat room const [currentRoom, setCurrentRoom] = useState(null); // Connect to socket when user logs in, disconnect when logged out useEffect(() => { if (!user?.token) { // If user logs out, close socket connection if (socketRef.current) { socketRef.current.disconnect(); socketRef.current = null; setIsConnected(false); setMySocketId(null); } return; } // If already connected, don't reconnect again if (socketRef.current?.connected) return; // Create socket connection with auth token const socket = io(`${API_BASE_URL}/chat`, { auth: { token: `Bearer ${user.token}` }, transports: ["websocket", "polling"], }); socketRef.current = socket; /* ---------------- Server Events ---------------- */ // When connection is successfully established socket.on("connect", () => { setIsConnected(true); }); // When server confirms who you are socket.on( "connected", (data: { YourId: string; username: string; message: string }) => { setMySocketId(data.YourId); console.log("[Socket] Connected:", data.message); }, ); // When socket disconnects socket.on("disconnect", () => { setIsConnected(false); setMySocketId(null); console.log("[Socket] Disconnected"); }); // If authentication fails socket.on("authError", (data: { mesage?: string }) => { console.error("[Socket] Auth error:", data.mesage); socket.disconnect(); }); // General server messages socket.on("serverNotice", (message: string) => { console.log("[Socket] Server notice:", message); }); //- Room Events // When you join a room socket.on("roomJoined", (data: { roomId: string; message: string }) => { console.log("[Socket] Room joined:", data.message); }); // When you leave a room socket.on("roomLeft", (data: { roomId: string }) => { console.log("[Socket] Room left:", data.roomId); }); // System messages inside a room socket.on("roomNotice", (data: RoomNotice) => { setNotices((prev) => [...prev, data]); }); // New chat message received socket.on("roomMessage", (data: ChatMessage) => { setMessages((prev) => [...prev, data]); }); // Room-related errors socket.on("roomError", (data: { message: string }) => { console.error("[Socket] Room error:", data.message); }); // Cleanup socket when component unmounts or token changes return () => { socket.disconnect(); socketRef.current = null; setIsConnected(false); setMySocketId(null); }; }, [user?.token]); // Actions you can call from UI // Join a chat room const joinRoom = useCallback((roomId: string) => { socketRef.current?.emit("joinRoom", { roomId }); }, []); // Leave a chat room const leaveRoom = useCallback((roomId: string) => { socketRef.current?.emit("leaveRoom", { roomId }); }, []); // Send a message to a room const sendMessage = useCallback((roomId: string, text: string) => { socketRef.current?.emit("roomMessage", { roomId, text }); }, []); // Clear all messages and notices (reset chat state) const clearMessages = useCallback(() => { setMessages([]); setNotices([]); }, []); return ( {children} ); } // Hook to use socket context // Custom hook to access socket anywhere in app export function useSocket() { const ctx = useContext(SocketContext); if (!ctx) throw new Error("useSocket must be used inside "); return ctx; }