diff --git a/frontend/src/app/home/page.tsx b/frontend/src/app/home/page.tsx
index e607ba9..0a39eb0 100644
--- a/frontend/src/app/home/page.tsx
+++ b/frontend/src/app/home/page.tsx
@@ -96,8 +96,7 @@ export default function Home() {
useEffect(() => {
connect();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ }, [connect]);
useEffect(() => {
if (!socket) return;
@@ -160,7 +159,11 @@ export default function Home() {
>
{!message.me && (
-
+
{onlineUsers[message.from] &&
onlineUsers[message.from].length > 5
? `${onlineUsers[message.from].slice(0, 1)}`
diff --git a/frontend/src/app/provide.tsx b/frontend/src/app/provide.tsx
index 84d73a3..1c827f1 100644
--- a/frontend/src/app/provide.tsx
+++ b/frontend/src/app/provide.tsx
@@ -8,15 +8,15 @@ type Prop = {
children: React.ReactNode;
};
export function AppProvider({ children }: Prop) {
- const [disconnect] = useSocketStore(({ disconnect }) => [disconnect]);
+ const disconnect = useSocketStore((state) => state.disconnect);
useEffect(() => {
+ window.addEventListener("beforeunload", disconnect);
return () => {
- console.log("disconnect");
+ window.removeEventListener("beforeunload", disconnect);
disconnect();
};
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ }, [disconnect]);
return (
diff --git a/frontend/src/components/ChatroomTitle.tsx b/frontend/src/components/ChatroomTitle.tsx
index e152aa0..5185a85 100644
--- a/frontend/src/components/ChatroomTitle.tsx
+++ b/frontend/src/components/ChatroomTitle.tsx
@@ -29,7 +29,6 @@ import {
} from "@tabler/icons-react";
import { SetStateAction, Dispatch, FC, useEffect, useState } from "react";
import useSocketStore from "@/store/socket";
-import { environment } from "@/config";
import Avatar from "./Avatar";
type Props = {
@@ -218,11 +217,7 @@ const ChatroomTitle: FC = ({ targetSocketId, setTargetSocketId }) => {
-
- {environment === "development"
- ? "Not available" // in development mode, hide online user list because it's a server side feature
- : "Online user"}
-
+ Online user
{socket?.connected &&
Object.keys(onlineUsers)
.filter((socketId) => socketId !== socket?.id)
diff --git a/frontend/src/pages/api/socket/broadcast.ts b/frontend/src/pages/api/socket/broadcast.ts
deleted file mode 100644
index 64177b4..0000000
--- a/frontend/src/pages/api/socket/broadcast.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { NextApiResponseServerIO, SocketBroadcastMessage } from "@/types/next";
-import { NextApiRequest } from "next";
-
-const broadcast = (req: NextApiRequest, res: NextApiResponseServerIO) => {
- if (req.method === "POST") {
- // get message
- const { from: sourceSocketId, timestamp, message } = req.body as SocketBroadcastMessage;
- console.log("PRODUCTION SERVER: Broadcast ", message)
- // dispatch to channel "message"
- res?.socket?.server?.io?.emit("broadcast", {
- from: sourceSocketId,
- message,
- timestamp,
- } as SocketBroadcastMessage);
- // return message
- res.status(201).json(message);
- }
-};
-
-export default broadcast;
diff --git a/frontend/src/pages/api/socket/private_message.ts b/frontend/src/pages/api/socket/private_message.ts
deleted file mode 100644
index 2f8ebee..0000000
--- a/frontend/src/pages/api/socket/private_message.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { NextApiResponseServerIO, SocketPrivateMessage } from "@/types/next";
-import { NextApiRequest } from "next";
-
-const private_message = (req: NextApiRequest, res: NextApiResponseServerIO) => {
- if (req.method === "POST") {
- // get message
- const {
- from: sourceSocketId,
- to: targetSocketId,
- timestamp,
- message,
- } = req.body as SocketPrivateMessage;
-
- // dispatch to channel "message"
- res?.socket?.server?.io?.to(targetSocketId).emit("private_message", {
- from: sourceSocketId,
- to: targetSocketId,
- message,
- timestamp,
- } as SocketPrivateMessage);
- res?.socket?.server?.io?.to(sourceSocketId).emit("private_message", {
- from: sourceSocketId,
- to: targetSocketId,
- message,
- timestamp,
- } as SocketPrivateMessage);
-
- // return message
- res.status(201).json(message);
- }
-};
-
-export default private_message;
diff --git a/frontend/src/pages/api/socket/socketio.ts b/frontend/src/pages/api/socket/socketio.ts
index 091991c..78a22d3 100644
--- a/frontend/src/pages/api/socket/socketio.ts
+++ b/frontend/src/pages/api/socket/socketio.ts
@@ -9,6 +9,10 @@ export const config = {
},
};
+const onlineUsers = new Map();
+let isEmitting = false;
+let sendOnlineUsers: NodeJS.Timeout;
+
const socketio = async (req: NextApiRequest, res: NextApiResponseServerIO) => {
if (!res.socket.server.io) {
console.log("MOCK SERVER: First connect on socket.io");
@@ -18,10 +22,62 @@ const socketio = async (req: NextApiRequest, res: NextApiResponseServerIO) => {
path: "/api/socket/socketio",
addTrailingSlash: false,
});
- io.on("connect", (socket) => {
- console.log("MOCK SERVER: SOCKET CONNECTED!", socket.id);
- }).on("disconnect", () => {
- console.log("MOCK SERVER: SOCKET DISCONNECTED!");
+ io.on("connection", (socket) => {
+ console.log("MOCK SERVER: user connected, online user count:", onlineUsers.size);
+
+ socket.on("join", (data) => {
+ const { socketId, name = socketId } = data;
+ onlineUsers.set(socketId, name);
+ // console.log(
+ // 'MOCK SERVER: user joined, online user count:',
+ // 'socketId: ',
+ // socketId,
+ // 'name: ',
+ // name,
+ // );
+ });
+
+ socket.on("broadcast", (broadcast, callback) => {
+ console.log("MOCK SERVER: Broadcast ", broadcast);
+ io.emit("broadcast", broadcast);
+ if (callback) {
+ callback({
+ ok: true,
+ });
+ }
+ });
+
+ socket.on("private_message", (message, callback) => {
+ console.log("MOCK SERVER: private_message", message);
+ const { from: sourceSocketId, to: targetSocketId } = message;
+ io.to(targetSocketId).emit("private_message", message);
+ io.to(sourceSocketId).emit("private_message", message);
+ if (callback) {
+ callback({
+ ok: true,
+ });
+ }
+ });
+
+ socket.on("disconnect", () => {
+ onlineUsers.delete(socket.id);
+ console.log(
+ "MOCK SERVER: user disconnected, online user count:",
+ onlineUsers.size
+ );
+ if (isEmitting && onlineUsers.size === 0) {
+ clearInterval(sendOnlineUsers);
+ isEmitting = false;
+ }
+ });
+
+ if (!isEmitting) {
+ sendOnlineUsers = setInterval(
+ () => io.emit("online_user", Object.fromEntries(onlineUsers)),
+ 5000
+ );
+ isEmitting = true;
+ }
});
// append SocketIO server to Next.js socket server response
res.socket.server.io = io;
diff --git a/frontend/src/store/socket.ts b/frontend/src/store/socket.ts
index 41ce245..b9863c4 100644
--- a/frontend/src/store/socket.ts
+++ b/frontend/src/store/socket.ts
@@ -12,8 +12,8 @@ type EmitModeDataTypes = {
type Store = {
socket: null | Socket;
- emitMode: "broadcast" | "private_message";
- setEmitMode: (mode: "broadcast" | "private_message") => void;
+ emitMode: keyof EmitModeDataTypes;
+ setEmitMode: (mode: keyof EmitModeDataTypes) => void;
emit: (event: T, data: EmitModeDataTypes[T]) => void;
connect: () => void;
disconnect: () => void;
@@ -33,30 +33,14 @@ const useSocketStore = create((set, get) => {
* @param data - The data to send along with the event.
*/
emit: (event, data) => {
- // console.log("emit", event, data);
- // Check if environment is development
- if (environment === "development") {
- // Send a POST request to the /api/socket/${event} endpoint with the data
- fetch(`/api/socket/${event}`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify(data),
- }).catch((error) => {
- // Display an error message if there was an error sending the request
- if (error instanceof Error) toast.error(error?.message);
- });
- } else {
- const { socket } = get();
- if (!socket) return toast.error("Socket not connected");
- // This callback response needs to define on server at first.
- // Emit the event with the data and handle the response
- socket.emit(event, data, (response: { ok: boolean }) => {
- // Display an error message if response.ok is false
- if (!response.ok) toast.error("Something went wrong");
- });
- }
+ const { socket } = get();
+ if (!socket) return toast.error("Socket not connected");
+ // This callback response needs to define on server at first.
+ // Emit the event with the data and handle the response
+ socket.emit(event, data, (response: { ok: boolean }) => {
+ // Display an error message if response.ok is false
+ if (!response.ok) toast.error("Something went wrong");
+ });
},
/**
* Connects to the socket server.