diff --git a/client/src/views/GameInterface/tabs/ChatTab.jsx b/client/src/views/GameInterface/tabs/ChatTab.jsx
index fcbe52e..bfdeaec 100644
--- a/client/src/views/GameInterface/tabs/ChatTab.jsx
+++ b/client/src/views/GameInterface/tabs/ChatTab.jsx
@@ -1,6 +1,7 @@
import React, { useState, useEffect, useRef } from "react";
import { useSocket } from "../../../hooks/useSocket";
import "./styles/ChatTab.css";
+import PlayerManager from "../../../services/PlayerManager";
const ChatTab = () => {
const { socket } = useSocket();
@@ -13,8 +14,16 @@ const ChatTab = () => {
const [showSidebar, setShowSidebar] = useState(true);
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
const [confirmUnfriend, setConfirmUnfriend] = useState(null);
+ const [onlineList, setOnlineList] = useState(
+ PlayerManager.onlinePlayers || [],
+ );
const messagesEndRef = useRef(null);
+ const activeChatRef = useRef(activeChat);
+
+ useEffect(() => {
+ activeChatRef.current = activeChat;
+ }, [activeChat]);
useEffect(() => {
const handleResize = () => {
@@ -30,17 +39,34 @@ const ChatTab = () => {
if (!socket) return;
socket.emit("friend:get_list");
+
if (activeChat === "global") {
socket.emit("chat:get_global_history");
+ } else {
+ socket.emit("chat:get_private_history", { friendId: activeChat });
}
+ }, [socket, activeChat]);
+
+ useEffect(() => {
+ if (!socket) return;
+
+ const interval = setInterval(() => {
+ setOnlineList([...PlayerManager.onlinePlayers]);
+ }, 3000);
const handleNewMessage = (msg) => {
- if (
- (activeChat === "global" && msg.type === "global") ||
- (activeChat !== "global" &&
- (msg.senderId === activeChat || msg.receiverId === activeChat))
- ) {
- setMessages((prev) => [...prev, msg]);
+ const currentActive = activeChatRef.current;
+ const isGlobalMatch = currentActive === "global" && msg.type === "global";
+ const isPrivateMatch =
+ currentActive !== "global" &&
+ msg.type === "private" &&
+ (msg.senderId === currentActive || msg.receiverId === currentActive);
+
+ if (isGlobalMatch || isPrivateMatch) {
+ setMessages((prev) => {
+ if (prev.some((m) => m.id === msg.id)) return prev;
+ return [...prev, msg];
+ });
}
};
@@ -50,16 +76,19 @@ const ChatTab = () => {
socket.on("chat:new_message", handleNewMessage);
socket.on("chat:global_history", handleHistory);
+ socket.on("chat:private_history", handleHistory);
socket.on("player:search_results", handleSearchResults);
socket.on("friend:list", handleFriendList);
return () => {
+ clearInterval(interval);
socket.off("chat:new_message", handleNewMessage);
socket.off("chat:global_history", handleHistory);
+ socket.off("chat:private_history", handleHistory);
socket.off("player:search_results", handleSearchResults);
socket.off("friend:list", handleFriendList);
};
- }, [socket, activeChat]);
+ }, [socket]);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
@@ -110,6 +139,10 @@ const ChatTab = () => {
return friend ? friend.username : "UNKNOWN_PILOT";
};
+ const isFriendOnline = (username) => {
+ return onlineList.includes(username);
+ };
+
return (
{confirmUnfriend && (
@@ -148,7 +181,6 @@ const ChatTab = () => {
onChange={handleSearch}
/>
-
{searchResults.length > 0 && (
{searchResults.map((player) => (
@@ -194,7 +226,7 @@ const ChatTab = () => {
onClick={() => selectChat(friend.id)}
>
{friend.username}
diff --git a/game-server/src/game/ChatManager.js b/game-server/src/game/ChatManager.js
index 0e79a4f..7e168e9 100644
--- a/game-server/src/game/ChatManager.js
+++ b/game-server/src/game/ChatManager.js
@@ -49,7 +49,31 @@ class ChatManager {
throw error;
}
}
-
+ async getPrivateHistory(myId, friendId, limit = 50) {
+ try {
+ return await Message.findAll({
+ where: {
+ type: "private",
+ [Op.or]: [
+ { senderId: myId, receiverId: friendId },
+ { senderId: friendId, receiverId: myId },
+ ],
+ },
+ limit,
+ order: [["createdAt", "ASC"]],
+ include: [
+ {
+ model: Player,
+ as: "sender",
+ attributes: ["username"],
+ },
+ ],
+ });
+ } catch (error) {
+ console.error("Private History Error:", error);
+ return [];
+ }
+ }
async searchPlayers(query, excludeId) {
try {
return await Player.findAll({
diff --git a/game-server/src/sockets/handlers/chatHandler.js b/game-server/src/sockets/handlers/chatHandler.js
index 1cdc2b4..6ceffda 100644
--- a/game-server/src/sockets/handlers/chatHandler.js
+++ b/game-server/src/sockets/handlers/chatHandler.js
@@ -14,7 +14,6 @@ module.exports = (io, socket) => {
socket.emit("chat:global_history", formattedHistory);
});
socket.on("chat:get_global_history", async () => {
- console.log(`Player ${socket.player?.id} requested chat history`);
const history = await chatManager.getGlobalHistory();
const formattedHistory = history.map((m) => ({
@@ -28,10 +27,12 @@ module.exports = (io, socket) => {
socket.emit("chat:global_history", formattedHistory);
});
+
socket.on("chat:send_message", async (payload) => {
try {
const senderId = socket.user?.id;
if (!senderId) return;
+
const messageData = await chatManager.saveMessage({
content: payload.content,
type: payload.type || "global",
@@ -42,20 +43,36 @@ module.exports = (io, socket) => {
if (messageData.type === "global") {
io.emit("chat:new_message", messageData);
} else {
- console.log(payload.receiverId);
socket
.to(`user_${payload.receiverId}`)
.emit("chat:new_message", messageData);
socket.emit("chat:new_message", messageData);
}
} catch (err) {
- console.log(err);
+ console.error(err);
socket.emit("error", { message: "CHAT_SEND_ERROR" });
}
});
+ socket.on("chat:get_private_history", async ({ friendId }) => {
+ const myId = socket.user?.id;
+
+ if (!myId || !friendId) return;
+
+ const history = await chatManager.getPrivateHistory(myId, friendId);
+ const formattedHistory = history.map((m) => ({
+ id: m.id,
+ content: m.content,
+ senderName: m.sender?.username || "Unknown",
+ senderId: m.senderId,
+ receiverId: m.receiverId,
+ createdAt: m.createdAt,
+ type: m.type,
+ }));
+ socket.emit("chat:private_history", formattedHistory);
+ });
socket.on("player:search", async ({ query }) => {
- const senderId = socket.player?.id;
+ const senderId = socket.user?.id;
if (!query || !senderId) return;
const results = await chatManager.searchPlayers(query, senderId);
diff --git a/game-server/src/sockets/handlers/connectionHandler.js b/game-server/src/sockets/handlers/connectionHandler.js
index e103c08..a129d95 100644
--- a/game-server/src/sockets/handlers/connectionHandler.js
+++ b/game-server/src/sockets/handlers/connectionHandler.js
@@ -63,7 +63,7 @@ module.exports = async (io, socket) => {
});
socket.broadcast.emit("player:joined", { username: playerRaw.username });
-
+ socket.join(`user_${socket.user.id}`);
socket.on("player:get_dashboard", async () => {
try {
const p = await Player.findByPk(userId);