From 8b71dbcc49a9413c541d6c21ba642fd15d6ae7ac Mon Sep 17 00:00:00 2001 From: SrashtiChauhan Date: Sat, 16 May 2026 18:25:02 +0530 Subject: [PATCH] fix: persist media messages and improve realtime chat handling --- backend/controllers/chat.controller.js | 22 +++---- backend/routes/chat.routes.js | 1 + backend/server.js | 3 +- frontend/app/chat/page.tsx | 82 +++++++++++++++++--------- 4 files changed, 70 insertions(+), 38 deletions(-) diff --git a/backend/controllers/chat.controller.js b/backend/controllers/chat.controller.js index 57fe855..670f059 100644 --- a/backend/controllers/chat.controller.js +++ b/backend/controllers/chat.controller.js @@ -13,30 +13,32 @@ export const getMessages = async (req, res) => { export const sendMessage = async (req, res) => { try { - const { text, username } = req.body; + const { text, username, image, audio } = req.body; - console.log("Incoming:", text, username); + console.log("Incoming:", { + text, + username, + image, + audio, + }); - if (!text || !username) { - return res.status(400).json({ error: "Text & username required" }); + if ((!text && !image && !audio) || !username) { + return res.status(400).json({error: "Message content & username required",}); } const { data, error } = await supabase .from("messages") - .insert([{ text, username, status: "sent" }]) + .insert([{ text, username, image, audio, status: "sent" }]) .select(); if (error) { console.error("Supabase error:", error); return res.status(500).json({ error: error.message }); } - const io = req.app.get("io"); - io.emit("newMessage", { - ...data[0], - status: "delivered", - }); + io.emit("newMessage", data[0]); + res.json(data); } catch (err) { diff --git a/backend/routes/chat.routes.js b/backend/routes/chat.routes.js index 98b7c28..abe287e 100644 --- a/backend/routes/chat.routes.js +++ b/backend/routes/chat.routes.js @@ -6,4 +6,5 @@ const router = express.Router(); router.get("/", getMessages); router.post("/", sendMessage); + export default router; \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 505365b..2f1d0f4 100644 --- a/backend/server.js +++ b/backend/server.js @@ -23,7 +23,8 @@ const onlineUsers = new Map(); // socket.id -> username const reactionsStore = {}; app.use(cors()); -app.use(express.json()); +app.use(express.json({ limit: "50mb" })); +app.use(express.urlencoded({ limit: "50mb", extended: true })); app.get("/", (req, res) => { res.send("FlowForge Backend Running 🚀"); diff --git a/frontend/app/chat/page.tsx b/frontend/app/chat/page.tsx index b58f641..5ceffb1 100644 --- a/frontend/app/chat/page.tsx +++ b/frontend/app/chat/page.tsx @@ -25,10 +25,10 @@ export default function ChatPage() { const [showEmojiPicker, setShowEmojiPicker] = useState(false); const [selectedImage, setSelectedImage] = useState(null); const [isRecording, setIsRecording] = useState(false); -const [audioBlob, setAudioBlob] = useState(null); + const [audioBlob, setAudioBlob] = useState(null); -const mediaRecorderRef = useRef(null); -const audioChunksRef = useRef([]); + const mediaRecorderRef = useRef(null); + const audioChunksRef = useRef([]); const socketRef = useRef(null); const typingTimeoutRef = useRef(null); @@ -51,6 +51,8 @@ const audioChunksRef = useRef([]); id: msg.id, user: msg.username, text: msg.text, + image: msg.image || undefined, + audio: msg.audio || undefined, time: new Date(msg.created_at).toLocaleTimeString(), status: msg.status || "sent", })); @@ -59,19 +61,30 @@ const audioChunksRef = useRef([]); // NEW MESSAGE socket.on("newMessage", (msg) => { - // prevent duplicate message for sender - if (msg.username === username) return; - - setMessages((prev) => [ - ...prev, - { - id: msg.id, - user: msg.username, - text: msg.text, - time: new Date(msg.created_at).toLocaleTimeString(), - status: msg.status, - }, - ]); + setMessages((prev) => { + const alreadyExists = prev.some( + (m) => + m.user === msg.username && + m.text === msg.text && + m.image === msg.image && + m.audio === msg.audio + ); + + if (alreadyExists) return prev; + + return [ + ...prev, + { + id: msg.id, + user: msg.username, + text: msg.text, + image: msg.image || undefined, + audio: msg.audio || undefined, + time: new Date(msg.created_at).toLocaleTimeString(), + status: msg.status, + }, + ]; + }); if (!isAtBottomRef.current) { setUnreadCount((prev) => prev + 1); @@ -142,18 +155,22 @@ socket.on("newMessage", (msg) => { async function handleSend() { if ((!input.trim() && !selectedImage && !audioBlob) || !username.trim()) return; + + const currentImage = selectedImage; + const currentAudio = audioBlob; const localMessage: Message = { id: Date.now().toString(), user: username, text: input, - image: selectedImage || undefined, - audio: audioBlob || undefined, time: new Date().toLocaleTimeString(), status: "sent", + + + ...(currentImage && { image: currentImage }), + ...(currentAudio && { audio: currentAudio }), }; - // immediately show message in UI - setMessages((prev) => [...prev, localMessage]); + // send text to backend await fetch("http://localhost:5000/api/chat", { @@ -164,6 +181,8 @@ socket.on("newMessage", (msg) => { body: JSON.stringify({ text: input, username, + image: currentImage, + audio: currentAudio, }), }); @@ -195,8 +214,13 @@ socket.on("newMessage", (msg) => { if (!file) return; - const imageUrl = URL.createObjectURL(file); - setSelectedImage(imageUrl); + const reader = new FileReader(); + + reader.onloadend = () => { + setSelectedImage(reader.result as string); + }; + + reader.readAsDataURL(file); } async function startRecording() { @@ -216,13 +240,17 @@ socket.on("newMessage", (msg) => { }; mediaRecorder.onstop = () => { - const audioBlob = new Blob(audioChunksRef.current, { + const blob = new Blob(audioChunksRef.current, { type: "audio/webm", }); - const audioUrl = URL.createObjectURL(audioBlob); + const reader = new FileReader(); + + reader.onloadend = () => { + setAudioBlob(reader.result as string); + }; - setAudioBlob(audioUrl); + reader.readAsDataURL(blob); }; mediaRecorder.start(); @@ -341,12 +369,12 @@ return ( )} {/* IMAGE */} - {msg.image && msg.image.startsWith("blob:") && ( + {msg.image && ( Shared image )} {/* AUDIO */} - {msg.audio && msg.audio.startsWith("blob:") && ( + {msg.audio && (