added server component, provider, api for login request to backend and receive jjwt token, socket context , and auth connection
This commit is contained in:
1 parent
a049c054e8
commit
6fa6415bfa
14 files changed
+6355
-190
No files matched your search
@@ -0,0 +1,223 @@
|
||||
"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<SocketContextValue | undefined>(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<Socket | null>(null);
|
||||
|
||||
// Connection status
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
|
||||
// Store current socket id assigned by server
|
||||
const [mySocketId, setMySocketId] = useState<string | null>(null);
|
||||
|
||||
// All chat messages received
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
||||
|
||||
// System notices (join/leave/info messages)
|
||||
const [notices, setNotices] = useState<RoomNotice[]>([]);
|
||||
|
||||
// Currently active chat room
|
||||
const [currentRoom, setCurrentRoom] = useState<string | null>(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 (
|
||||
<SocketContext.Provider
|
||||
value={{
|
||||
socket: socketRef.current,
|
||||
isConnected,
|
||||
mySocketId,
|
||||
joinRoom,
|
||||
leaveRoom,
|
||||
sendMessage,
|
||||
messages,
|
||||
notices,
|
||||
currentRoom,
|
||||
setCurrentRoom,
|
||||
clearMessages,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SocketContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
// 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 <SocketProvider>");
|
||||
return ctx;
|
||||
}
|
||||
Reference in new issue
Block a user