diff --git a/backend/collab-service/.env.sample b/backend/collab-service/.env.sample index 94020b37a8..65939ae031 100644 --- a/backend/collab-service/.env.sample +++ b/backend/collab-service/.env.sample @@ -4,6 +4,9 @@ SERVICE_PORT=3003 # Origins for cors ORIGINS=http://localhost:5173,http://127.0.0.1:5173 +# Other service APIs +USER_SERVICE_URL=http://user-service:3001/api + # Redis configuration REDIS_URI=redis://collab-service-redis:6379 diff --git a/backend/collab-service/src/api/userService.ts b/backend/collab-service/src/api/userService.ts new file mode 100644 index 0000000000..88442a3b6c --- /dev/null +++ b/backend/collab-service/src/api/userService.ts @@ -0,0 +1,15 @@ +import axios from "axios"; + +const USER_SERVICE_URL = + process.env.USER_SERVICE_URL || "http://localhost:3001/api"; + +const userClient = axios.create({ + baseURL: USER_SERVICE_URL, + withCredentials: true, +}); + +export const verifyToken = (token: string | undefined) => { + return userClient.get("/auth/verify-token", { + headers: { authorization: token }, + }); +}; diff --git a/backend/collab-service/src/middlewares/basicAccessControl.ts b/backend/collab-service/src/middlewares/basicAccessControl.ts new file mode 100644 index 0000000000..727ee6783e --- /dev/null +++ b/backend/collab-service/src/middlewares/basicAccessControl.ts @@ -0,0 +1,19 @@ +import { ExtendedError, Socket } from "socket.io"; +import { verifyToken } from "../api/userService.ts"; + +export const verifyUserToken = ( + socket: Socket, + next: (err?: ExtendedError) => void +) => { + const token = + socket.handshake.headers.authorization || socket.handshake.auth.token; + verifyToken(token) + .then(() => { + console.log("Valid credentials"); + next(); + }) + .catch((err) => { + console.error(err); + next(new Error("Unauthorized")); + }); +}; diff --git a/backend/collab-service/src/server.ts b/backend/collab-service/src/server.ts index c1d11c7333..1a00c9c42c 100644 --- a/backend/collab-service/src/server.ts +++ b/backend/collab-service/src/server.ts @@ -3,8 +3,10 @@ import app, { allowedOrigins } from "./app.ts"; import { handleWebsocketCollabEvents } from "./handlers/websocketHandler.ts"; import { Server, Socket } from "socket.io"; import { connectRedis } from "./config/redis.ts"; +import { verifyUserToken } from "./middlewares/basicAccessControl.ts"; const server = http.createServer(app); + export const io = new Server(server, { cors: { origin: allowedOrigins, @@ -13,6 +15,8 @@ export const io = new Server(server, { connectionStateRecovery: {}, }); +io.use(verifyUserToken); + io.on("connection", (socket: Socket) => { handleWebsocketCollabEvents(socket); }); diff --git a/backend/communication-service/.env.sample b/backend/communication-service/.env.sample index e5f4ba279e..702bd12d55 100644 --- a/backend/communication-service/.env.sample +++ b/backend/communication-service/.env.sample @@ -3,3 +3,6 @@ SERVER_PORT=3005 # Origins for cors ORIGINS=http://localhost:5173,http://127.0.0.1:5173 + +# Other service APIs +USER_SERVICE_URL=http://user-service:3001/api diff --git a/backend/communication-service/README.md b/backend/communication-service/README.md index b0f30a74e9..f1e24f9098 100644 --- a/backend/communication-service/README.md +++ b/backend/communication-service/README.md @@ -30,19 +30,23 @@ - Select the `Socket.IO` option and set URL to `http://localhost:3005`. Click `Connect`. - ![image1.png](./docs/image1.png) + ![image1.png](./docs/images/postman-setup1.png) - Add the following events in the `Events` tab and listen to them. - ![image2.png](./docs/image2.png) + ![image2.png](./docs/images/postman-setup2.png) - - To send a message, go to the `Message` tab and ensure that your message is being parsed as `JSON`. + - Add a valid JWT token in the `Authorization` header. - ![image3.png](./docs/image3.png) + ![image3.png](./docs/images/postman-setup3.png) - In the `Event name` input, input the correct event name. Click on `Send` to send a message. - ![image4.png](./docs/image4.png) + ![image4.png](./docs/images/postman-setup4.png) + + - To send a message, go to the `Message` tab and ensure that your message is being parsed as `JSON`. + + ![image5.png](./docs/images/postman-setup5.png) ## Events Available @@ -50,5 +54,5 @@ | --------------------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | **join** | Joins a communication rooms | `roomId` (string): ID of the room.
`username` (string): Username of the user that joined. | **user_joined**: Notify the other user that a new user has joined the room. | | **send_text_message** | Sends a message to the other user | `roomId` (string): ID of the room.
`message` (string): Message to send.
`username` (string): User that sent the message.
`createdTime` (number): Time that the user sent the message. | **text_message_received**: Notify the user that a message is sent | -| **leave** | Leaves the communication room. | `roomId` (string): ID of the room to leave.
`username` (string): User that wants to leave. | **user_left**: To notify the user when one user leaves. | +| **user_disconnect** | User disconnection. | None | **user_left**: To notify the user when one user leaves. | | **disconnect** | Disconnects from the server. | None | **disconnected**: To notify the user when one user gets disconnected from the server. | diff --git a/backend/communication-service/docs/image2.png b/backend/communication-service/docs/image2.png deleted file mode 100644 index e422ba0462..0000000000 Binary files a/backend/communication-service/docs/image2.png and /dev/null differ diff --git a/backend/communication-service/docs/image1.png b/backend/communication-service/docs/images/postman-setup1.png similarity index 100% rename from backend/communication-service/docs/image1.png rename to backend/communication-service/docs/images/postman-setup1.png diff --git a/backend/communication-service/docs/images/postman-setup2.png b/backend/communication-service/docs/images/postman-setup2.png new file mode 100644 index 0000000000..d361fb1ce3 Binary files /dev/null and b/backend/communication-service/docs/images/postman-setup2.png differ diff --git a/backend/communication-service/docs/images/postman-setup3.png b/backend/communication-service/docs/images/postman-setup3.png new file mode 100644 index 0000000000..3b0a722fdc Binary files /dev/null and b/backend/communication-service/docs/images/postman-setup3.png differ diff --git a/backend/communication-service/docs/image4.png b/backend/communication-service/docs/images/postman-setup4.png similarity index 100% rename from backend/communication-service/docs/image4.png rename to backend/communication-service/docs/images/postman-setup4.png diff --git a/backend/communication-service/docs/image3.png b/backend/communication-service/docs/images/postman-setup5.png similarity index 100% rename from backend/communication-service/docs/image3.png rename to backend/communication-service/docs/images/postman-setup5.png diff --git a/backend/communication-service/package-lock.json b/backend/communication-service/package-lock.json index 20d788705e..11548b5a81 100644 --- a/backend/communication-service/package-lock.json +++ b/backend/communication-service/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "axios": "^1.7.7", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.1", @@ -2492,6 +2493,23 @@ "dev": true, "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -2907,6 +2925,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3091,6 +3121,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3948,6 +3987,40 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -5869,6 +5942,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/backend/communication-service/package.json b/backend/communication-service/package.json index c7528cb505..08fcfb3a6b 100644 --- a/backend/communication-service/package.json +++ b/backend/communication-service/package.json @@ -14,6 +14,7 @@ "license": "ISC", "description": "", "dependencies": { + "axios": "^1.7.7", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.1", diff --git a/backend/communication-service/src/api/userService.ts b/backend/communication-service/src/api/userService.ts new file mode 100644 index 0000000000..88442a3b6c --- /dev/null +++ b/backend/communication-service/src/api/userService.ts @@ -0,0 +1,15 @@ +import axios from "axios"; + +const USER_SERVICE_URL = + process.env.USER_SERVICE_URL || "http://localhost:3001/api"; + +const userClient = axios.create({ + baseURL: USER_SERVICE_URL, + withCredentials: true, +}); + +export const verifyToken = (token: string | undefined) => { + return userClient.get("/auth/verify-token", { + headers: { authorization: token }, + }); +}; diff --git a/backend/communication-service/src/handlers/websocketHandler.ts b/backend/communication-service/src/handlers/websocketHandler.ts index 92658abdf2..959c80d64e 100644 --- a/backend/communication-service/src/handlers/websocketHandler.ts +++ b/backend/communication-service/src/handlers/websocketHandler.ts @@ -9,6 +9,7 @@ export const handleWebsocketCommunicationEvents = (socket: Socket) => { CommunicationEvents.JOIN, async ({ roomId, username }: { roomId: string; username: string }) => { connectUser(username); + console.log(username, roomId); const room = io.sockets.adapter.rooms.get(roomId); if (room?.has(socket.id)) { socket.emit(CommunicationEvents.ALREADY_JOINED); diff --git a/backend/communication-service/src/middlewares/basicAccessControl.ts b/backend/communication-service/src/middlewares/basicAccessControl.ts new file mode 100644 index 0000000000..15088e9a86 --- /dev/null +++ b/backend/communication-service/src/middlewares/basicAccessControl.ts @@ -0,0 +1,19 @@ +import { ExtendedError, Socket } from "socket.io"; +import { verifyToken } from "../api/userService"; + +export const verifyUserToken = ( + socket: Socket, + next: (err?: ExtendedError) => void +) => { + const token = + socket.handshake.headers.authorization || socket.handshake.auth.token; + verifyToken(token) + .then(() => { + console.log("Valid credentials"); + next(); + }) + .catch((err) => { + console.error(err); + next(new Error("Unauthorized")); + }); +}; diff --git a/backend/communication-service/src/server.ts b/backend/communication-service/src/server.ts index d3a5e530bd..5421fe6f17 100644 --- a/backend/communication-service/src/server.ts +++ b/backend/communication-service/src/server.ts @@ -2,16 +2,19 @@ import app, { allowedOrigins } from "./app"; import { createServer } from "http"; import { Server } from "socket.io"; import { handleWebsocketCommunicationEvents } from "./handlers/websocketHandler"; +import { verifyUserToken } from "./middlewares/basicAccessControl"; const PORT = process.env.SERVICE_PORT || 3005; const server = createServer(app); export const io = new Server(server, { - cors: { origin: allowedOrigins, methods: ["GET", "POST"] }, + cors: { origin: allowedOrigins, methods: ["GET", "POST"], credentials: true }, connectionStateRecovery: {}, }); +io.use(verifyUserToken); + io.on("connection", handleWebsocketCommunicationEvents); server.listen(PORT, () => { diff --git a/backend/matching-service/.env.sample b/backend/matching-service/.env.sample index 83217a9e53..044f09d289 100644 --- a/backend/matching-service/.env.sample +++ b/backend/matching-service/.env.sample @@ -7,6 +7,7 @@ ORIGINS=http://localhost:5173,http://127.0.0.1:5173 # Other service APIs QUESTION_SERVICE_URL=http://question-service:3000/api/questions QN_HISTORY_SERVICE_URL=http://qn-history-service:3006/api/qnhistories +USER_SERVICE_URL=http://user-service:3001/api # RabbitMq configuration RABBITMQ_DEFAULT_USER=admin diff --git a/backend/matching-service/src/api/questionHistoryService.ts b/backend/matching-service/src/api/questionHistoryService.ts new file mode 100644 index 0000000000..0a17c16703 --- /dev/null +++ b/backend/matching-service/src/api/questionHistoryService.ts @@ -0,0 +1,31 @@ +import axios from "axios"; + +const QN_HISTORY_SERVICE_URL = + process.env.QN_HISTORY_SERVICE_URL || + "http://qn-history-service:3006/api/qnhistories"; + +const qnHistoryService = axios.create({ + baseURL: QN_HISTORY_SERVICE_URL, + headers: { + "Content-Type": "application/json", + }, +}); + +export const createQuestionHistory = ( + questionId: string, + title: string, + submissionStatus: string, + language: string, + ...userIds: string[] +) => { + const dateAttempted = new Date(); + return qnHistoryService.post("/", { + userIds, + questionId, + title, + submissionStatus, + language, + dateAttempted, + timeTaken: 0, + }); +}; diff --git a/backend/matching-service/src/api/questionService.ts b/backend/matching-service/src/api/questionService.ts new file mode 100644 index 0000000000..7599df3bc2 --- /dev/null +++ b/backend/matching-service/src/api/questionService.ts @@ -0,0 +1,16 @@ +import axios from "axios"; + +const QUESTION_SERVICE_URL = + process.env.QUESTION_SERVICE_URL || + "http://question-service:3000/api/questions"; + +const questionClient = axios.create({ + baseURL: QUESTION_SERVICE_URL, + headers: { + "Content-Type": "application/json", + }, +}); + +export const getRandomQuestion = (complexity: string, category: string) => { + return questionClient.get("/random", { params: { complexity, category } }); +}; diff --git a/backend/matching-service/src/api/userService.ts b/backend/matching-service/src/api/userService.ts new file mode 100644 index 0000000000..88442a3b6c --- /dev/null +++ b/backend/matching-service/src/api/userService.ts @@ -0,0 +1,15 @@ +import axios from "axios"; + +const USER_SERVICE_URL = + process.env.USER_SERVICE_URL || "http://localhost:3001/api"; + +const userClient = axios.create({ + baseURL: USER_SERVICE_URL, + withCredentials: true, +}); + +export const verifyToken = (token: string | undefined) => { + return userClient.get("/auth/verify-token", { + headers: { authorization: token }, + }); +}; diff --git a/backend/matching-service/src/config/rabbitmq.ts b/backend/matching-service/src/config/rabbitmq.ts index 966e98cbd6..0623f88b02 100644 --- a/backend/matching-service/src/config/rabbitmq.ts +++ b/backend/matching-service/src/config/rabbitmq.ts @@ -1,6 +1,6 @@ import amqplib, { Connection } from "amqplib"; import dotenv from "dotenv"; -import { matchUsers } from "../utils/mq_utils"; +import { matchUsers } from "../utils/messageQueue"; import { MatchRequestItem } from "../handlers/matchHandler"; import { Complexities, Categories, Languages } from "../utils/constants"; diff --git a/backend/matching-service/src/handlers/websocketHandler.ts b/backend/matching-service/src/handlers/websocketHandler.ts index a738530bfc..74cdd526e9 100644 --- a/backend/matching-service/src/handlers/websocketHandler.ts +++ b/backend/matching-service/src/handlers/websocketHandler.ts @@ -11,7 +11,8 @@ import { } from "./matchHandler"; import { io } from "../server"; import { v4 as uuidv4 } from "uuid"; -import { qnHistoryService, questionService } from "../utils/api"; +import { getRandomQuestion } from "../api/questionService"; +import { createQuestionHistory } from "../api/questionHistoryService"; enum MatchEvents { // Receive @@ -130,28 +131,23 @@ export const handleWebsocketMatchEvents = (socket: Socket) => { } const { complexity, category, language } = match; - questionService - .get("/random", { params: { complexity, category } }) - .then((res) => { - const qnId = res.data.question.id; - qnHistoryService - .post("/", { - userIds: [userId1, userId2], - questionId: qnId, - title: res.data.question.title, - submissionStatus: "Attempted", - dateAttempted: new Date(), - timeTaken: 0, - language: language, - }) - .then((res) => { - io.to(matchId).emit( - MatchEvents.MATCH_SUCCESSFUL, - qnId, - res.data.qnHistory.id - ); - }); + getRandomQuestion(complexity, category).then((res) => { + const qnId = res.data.question.id; + createQuestionHistory( + qnId, + res.data.question.title, + "Attempted", + language, + userId1, + userId2 + ).then((res) => { + io.to(matchId).emit( + MatchEvents.MATCH_SUCCESSFUL, + qnId, + res.data.qnHistory.id + ); }); + }); } } ); diff --git a/backend/matching-service/src/middlewares/basicAccessControl.ts b/backend/matching-service/src/middlewares/basicAccessControl.ts new file mode 100644 index 0000000000..15088e9a86 --- /dev/null +++ b/backend/matching-service/src/middlewares/basicAccessControl.ts @@ -0,0 +1,19 @@ +import { ExtendedError, Socket } from "socket.io"; +import { verifyToken } from "../api/userService"; + +export const verifyUserToken = ( + socket: Socket, + next: (err?: ExtendedError) => void +) => { + const token = + socket.handshake.headers.authorization || socket.handshake.auth.token; + verifyToken(token) + .then(() => { + console.log("Valid credentials"); + next(); + }) + .catch((err) => { + console.error(err); + next(new Error("Unauthorized")); + }); +}; diff --git a/backend/matching-service/src/server.ts b/backend/matching-service/src/server.ts index fc627d78ca..0c420ce87a 100644 --- a/backend/matching-service/src/server.ts +++ b/backend/matching-service/src/server.ts @@ -3,8 +3,11 @@ import app, { allowedOrigins } from "./app.ts"; import { handleWebsocketMatchEvents } from "./handlers/websocketHandler.ts"; import { Server } from "socket.io"; import { connectToRabbitMq } from "./config/rabbitmq.ts"; +import { verifyToken } from "./api/userService.ts"; +import { verifyUserToken } from "./middlewares/basicAccessControl.ts"; const server = http.createServer(app); + export const io = new Server(server, { cors: { origin: allowedOrigins, @@ -13,6 +16,8 @@ export const io = new Server(server, { connectionStateRecovery: {}, }); +io.use(verifyUserToken); + io.on("connection", (socket) => { handleWebsocketMatchEvents(socket); }); diff --git a/backend/matching-service/src/utils/api.ts b/backend/matching-service/src/utils/api.ts deleted file mode 100644 index 1b088f4c05..0000000000 --- a/backend/matching-service/src/utils/api.ts +++ /dev/null @@ -1,23 +0,0 @@ -import axios from "axios"; - -const QUESTION_SERVICE_URL = - process.env.QUESTION_SERVICE_URL || - "http://question-service:3000/api/questions"; - -const QN_HISTORY_SERVICE_URL = - process.env.QN_HISTORY_SERVICE_URL || - "http://qn-history-service:3006/api/qnhistories"; - -export const questionService = axios.create({ - baseURL: QUESTION_SERVICE_URL, - headers: { - "Content-Type": "application/json", - }, -}); - -export const qnHistoryService = axios.create({ - baseURL: QN_HISTORY_SERVICE_URL, - headers: { - "Content-Type": "application/json", - }, -}); diff --git a/backend/matching-service/src/utils/mq_utils.ts b/backend/matching-service/src/utils/messageQueue.ts similarity index 100% rename from backend/matching-service/src/utils/mq_utils.ts rename to backend/matching-service/src/utils/messageQueue.ts diff --git a/docker-compose.yml b/docker-compose.yml index 9af939d783..1d3067ba03 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -95,6 +95,8 @@ services: env_file: ./backend/communication-service/.env ports: - 3005:3005 + depends_on: + - user-service networks: - peerprep-network volumes: diff --git a/frontend/src/components/Chat/index.tsx b/frontend/src/components/Chat/index.tsx index ed2ccb0003..7bea1c2770 100644 --- a/frontend/src/components/Chat/index.tsx +++ b/frontend/src/components/Chat/index.tsx @@ -10,6 +10,7 @@ import { USE_MATCH_ERROR_MESSAGE, } from "../../utils/constants"; import { useAuth } from "../../contexts/AuthContext"; +import { toast } from "react-toastify"; type Message = { from: string; @@ -36,6 +37,7 @@ const Chat: React.FC = ({ isActive }) => { const match = useMatch(); const auth = useAuth(); const messagesRef = useRef(null); + const errorHandledRef = useRef(false); if (!match) { throw new Error(USE_MATCH_ERROR_MESSAGE); @@ -68,10 +70,17 @@ const Chat: React.FC = ({ isActive }) => { const listener = (message: Message) => { setMessages((prevMessages) => [...prevMessages, message]); }; + const errorListener = () => { + if (!errorHandledRef.current) { + toast.error("Connection error. Please try again."); + errorHandledRef.current = true; + } + }; communicationSocket.on(CommunicationEvents.USER_JOINED, listener); communicationSocket.on(CommunicationEvents.TEXT_MESSAGE_RECEIVED, listener); communicationSocket.on(CommunicationEvents.DISCONNECTED, listener); + communicationSocket.on(CommunicationEvents.CONNECT_ERROR, errorListener); return () => { communicationSocket.off(CommunicationEvents.USER_JOINED, listener); @@ -80,6 +89,7 @@ const Chat: React.FC = ({ isActive }) => { listener ); communicationSocket.off(CommunicationEvents.DISCONNECTED, listener); + communicationSocket.off(CommunicationEvents.CONNECT_ERROR, errorListener); }; }, []); diff --git a/frontend/src/utils/collabSocket.ts b/frontend/src/utils/collabSocket.ts index 4fe50bafe3..1d252552cc 100644 --- a/frontend/src/utils/collabSocket.ts +++ b/frontend/src/utils/collabSocket.ts @@ -33,9 +33,13 @@ export type CollabSessionData = { }; const COLLAB_SOCKET_URL = "http://localhost:3003"; + export const collabSocket = io(COLLAB_SOCKET_URL, { reconnectionAttempts: 3, autoConnect: false, + auth: { + token: `Bearer ${localStorage.getItem("token")}`, + }, }); let doc: Doc; diff --git a/frontend/src/utils/communicationSocket.ts b/frontend/src/utils/communicationSocket.ts index 51cc908a10..cff0006ddb 100644 --- a/frontend/src/utils/communicationSocket.ts +++ b/frontend/src/utils/communicationSocket.ts @@ -11,6 +11,7 @@ export enum CommunicationEvents { USER_JOINED = "user_joined", ALREADY_JOINED = "already_joined", TEXT_MESSAGE_RECEIVED = "text_message_received", + CONNECT_ERROR = "connect_error", DISCONNECTED = "disconnected", } @@ -19,4 +20,8 @@ const COMMUNICATION_SOCKET_URL = "http://localhost:3005"; export const communicationSocket = io(COMMUNICATION_SOCKET_URL, { reconnectionAttempts: 3, autoConnect: false, + withCredentials: true, + auth: { + token: `Bearer ${localStorage.getItem("token")}`, + }, }); diff --git a/frontend/src/utils/matchSocket.ts b/frontend/src/utils/matchSocket.ts index e79801a6b4..4ef4d2038b 100644 --- a/frontend/src/utils/matchSocket.ts +++ b/frontend/src/utils/matchSocket.ts @@ -5,4 +5,7 @@ const MATCH_SOCKET_URL = "http://localhost:3002"; export const matchSocket = io(MATCH_SOCKET_URL, { reconnectionAttempts: 3, autoConnect: false, + auth: { + token: `Bearer ${localStorage.getItem("token")}`, + }, });