diff --git a/client/src/components/production-board-kanban/production-board-kanban.container.jsx b/client/src/components/production-board-kanban/production-board-kanban.container.jsx index 643204469..07164c63c 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.container.jsx +++ b/client/src/components/production-board-kanban/production-board-kanban.container.jsx @@ -23,6 +23,9 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionTyp const fired = useRef(false); const client = useApolloClient(); const { socket } = useContext(SocketContext); // Get the socket from context + const reconnectTimeout = useRef(null); // To store the reconnect timeout + const disconnectTime = useRef(null); // To track disconnection time + const acceptableReconnectTime = 2000; // 2 seconds threshold const { treatments: { Websocket_Production } @@ -126,19 +129,44 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionTyp } }; - const handleReconnect = () => { - //If we were disconnected from the board, we missed stuff. We need to refresh it entirely. - if (refetch) refetch(); + const handleDisconnect = () => { + // Capture the disconnection time + disconnectTime.current = Date.now(); }; - // Listen for 'job-changed' events + + const handleReconnect = () => { + const reconnectTime = Date.now(); + const disconnectionDuration = reconnectTime - disconnectTime.current; + + // Only refetch if disconnection was longer than the acceptable reconnect time + if (disconnectionDuration >= acceptableReconnectTime) { + if (!reconnectTimeout.current) { + reconnectTimeout.current = setTimeout(() => { + const randomDelay = Math.floor(Math.random() * (30000 - 10000 + 1)) + 10000; // Random delay between 10 and 30 seconds + setTimeout(() => { + if (refetch) refetch().catch((err) => console.error(`Issue `)); + reconnectTimeout.current = null; // Clear the timeout reference after refetch + }, randomDelay); + }, acceptableReconnectTime); + } + } + }; + + // Listen for 'job-changed', 'disconnect', and 'connect' events socket.on("production-job-updated", handleJobUpdates); - socket.on("reconnect", handleReconnect); + socket.on("disconnect", handleDisconnect); + socket.on("connect", handleReconnect); + // Clean up on unmount or when dependencies change return () => { socket.off("production-job-updated", handleJobUpdates); - socket.off("reconnect", handleReconnect); + socket.off("disconnect", handleDisconnect); + socket.off("connect", handleReconnect); + if (reconnectTimeout.current) { + clearTimeout(reconnectTimeout.current); + } }; - }, [subscriptionEnabled, socket, bodyshop, data, client, refetch]); + }, [subscriptionEnabled, socket, bodyshop, client, refetch]); const filteredAssociationSettings = useMemo(() => { return associationSettings?.associations[0] || null; diff --git a/client/src/components/production-list-table/production-list-table.container.jsx b/client/src/components/production-list-table/production-list-table.container.jsx index fedcf3ff5..aa0ccdd85 100644 --- a/client/src/components/production-list-table/production-list-table.container.jsx +++ b/client/src/components/production-list-table/production-list-table.container.jsx @@ -1,5 +1,5 @@ import { useApolloClient, useQuery, useSubscription } from "@apollo/client"; -import React, { useContext, useEffect, useState } from "react"; +import React, { useContext, useEffect, useState, useRef } from "react"; import { QUERY_EXACT_JOB_IN_PRODUCTION, QUERY_EXACT_JOBS_IN_PRODUCTION, @@ -16,6 +16,10 @@ export default function ProductionListTableContainer({ bodyshop, subscriptionTyp const client = useApolloClient(); const { socket } = useContext(SocketContext); const [joblist, setJoblist] = useState([]); + const reconnectTimeout = useRef(null); // To store the reconnect timeout + const disconnectTime = useRef(null); // To store the time of disconnection + + const acceptableReconnectTime = 2000; // 2 seconds threshold // Get Split treatment const { @@ -128,18 +132,47 @@ export default function ProductionListTableContainer({ bodyshop, subscriptionTyp } } }; - const handleReconnect = () => { - //If we were disconnected from the board, we missed stuff. We need to refresh it entirely. - if (refetch) refetch(); + + const handleDisconnect = () => { + // Capture the time when the disconnection happens + disconnectTime.current = Date.now(); }; - // Listen for 'production-job-updated' events + + const handleReconnect = () => { + // Calculate how long the disconnection lasted + const reconnectTime = Date.now(); + const disconnectionDuration = reconnectTime - disconnectTime.current; + + // If disconnection lasted less than acceptable reconnect time, do nothing + if (disconnectionDuration < acceptableReconnectTime) { + return; + } + + // Schedule a refetch with a random delay between 10 and 30 seconds + if (!reconnectTimeout.current) { + reconnectTimeout.current = setTimeout(() => { + const randomDelay = Math.floor(Math.random() * (30000 - 10000 + 1)) + 10000; // Random delay between 10 and 30 seconds + setTimeout(() => { + if (refetch) refetch(); + reconnectTimeout.current = null; // Clear the timeout reference after refetch + }, randomDelay); + }, acceptableReconnectTime); + } + }; + + // Listen for 'production-job-updated', 'disconnect', and 'connect' events socket.on("production-job-updated", handleJobUpdates); - socket.on("reconnect", handleReconnect); + socket.on("disconnect", handleDisconnect); + socket.on("connect", handleReconnect); // Clean up on unmount or when dependencies change return () => { socket.off("production-job-updated", handleJobUpdates); - socket.off("reconnect", handleReconnect); + socket.off("disconnect", handleDisconnect); + socket.off("connect", handleReconnect); + if (reconnectTimeout.current) { + clearTimeout(reconnectTimeout.current); + } }; }, [subscriptionEnabled, socket, bodyshop, client, refetch]); @@ -151,6 +184,7 @@ export default function ProductionListTableContainer({ bodyshop, subscriptionTyp fetchPolicy: "network-only" }); }; + const getUpdatedJobsData = (jobIds) => { client.query({ query: QUERY_EXACT_JOBS_IN_PRODUCTION, diff --git a/server/web-sockets/redisSocketEvents.js b/server/web-sockets/redisSocketEvents.js index b090ddc3d..eb107f75e 100644 --- a/server/web-sockets/redisSocketEvents.js +++ b/server/web-sockets/redisSocketEvents.js @@ -30,52 +30,72 @@ const redisSocketEvents = (io, { addUserToRoom, getUsersInRoom, removeUserFromRo // Room management and broadcasting events function registerRoomAndBroadcastEvents(socket) { socket.on("join-bodyshop-room", async (bodyshopUUID) => { - const room = getBodyshopRoom(bodyshopUUID); - socket.join(room); - await addUserToRoom(room, { uid: socket.user.uid, email: socket.user.email }); - createLogEvent(socket, "DEBUG", `Client joined bodyshop room: ${bodyshopUUID}`); + try { + const room = getBodyshopRoom(bodyshopUUID); + socket.join(room); + await addUserToRoom(room, { uid: socket.user.uid, email: socket.user.email }); + createLogEvent(socket, "DEBUG", `Client joined bodyshop room: ${bodyshopUUID}`); - // Notify all users in the room about the updated user list - const usersInRoom = await getUsersInRoom(bodyshopUUID); - io.to(room).emit("room-users-updated", usersInRoom); + // Notify all users in the room about the updated user list + const usersInRoom = await getUsersInRoom(room); + io.to(room).emit("room-users-updated", usersInRoom); + } catch (error) { + createLogEvent(socket, "ERROR", `Error joining room: ${error}`); + } }); socket.on("leave-bodyshop-room", async (bodyshopUUID) => { - const room = getBodyshopRoom(bodyshopUUID); - socket.leave(room); - createLogEvent(socket, "DEBUG", `Client left bodyshop room: ${room}`); + try { + const room = getBodyshopRoom(bodyshopUUID); + socket.leave(room); + createLogEvent(socket, "DEBUG", `Client left bodyshop room: ${room}`); + } catch (error) { + createLogEvent(socket, "ERROR", `Error joining room: ${error}`); + } }); socket.on("get-room-users", async (bodyshopUUID, callback) => { - const usersInRoom = await getUsersInRoom(getBodyshopRoom(bodyshopUUID)); - callback(usersInRoom); + try { + const usersInRoom = await getUsersInRoom(getBodyshopRoom(bodyshopUUID)); + callback(usersInRoom); + } catch (error) { + createLogEvent(socket, "ERROR", `Error getting room: ${error}`); + } }); socket.on("broadcast-to-bodyshop", async (bodyshopUUID, message) => { - const room = getBodyshopRoom(bodyshopUUID); - io.to(room).emit("bodyshop-message", message); - createLogEvent(socket, "INFO", `Broadcast message to bodyshop ${room}`); + try { + const room = getBodyshopRoom(bodyshopUUID); + io.to(room).emit("bodyshop-message", message); + createLogEvent(socket, "INFO", `Broadcast message to bodyshop ${room}`); + } catch (error) { + createLogEvent(socket, "ERROR", `Error getting room: ${error}`); + } }); socket.on("disconnect", async () => { - createLogEvent(socket, "DEBUG", `User disconnected.`); + try { + createLogEvent(socket, "DEBUG", `User disconnected.`); - // Get all rooms the socket is part of - const rooms = Array.from(socket.rooms).filter((room) => room !== socket.id); + // Get all rooms the socket is part of + const rooms = Array.from(socket.rooms).filter((room) => room !== socket.id); - for (const bodyshopRoom of rooms) { - await removeUserFromRoom(bodyshopRoom, { uid: socket.user.uid, email: socket.user.email }); + for (const bodyshopRoom of rooms) { + await removeUserFromRoom(bodyshopRoom, { uid: socket.user.uid, email: socket.user.email }); - // Notify all users in the room about the updated user list - const usersInRoom = await getUsersInRoom(bodyshopRoom); - io.to(bodyshopRoom).emit("room-users-updated", usersInRoom); + // Notify all users in the room about the updated user list + const usersInRoom = await getUsersInRoom(bodyshopRoom); + io.to(bodyshopRoom).emit("room-users-updated", usersInRoom); + } + } catch (error) { + createLogEvent(socket, "ERROR", `Error getting room: ${error}`); } }); } // Register all socket events for a given socket connection function registerSocketEvents(socket) { - createLogEvent(socket, "DEBUG", `Connected and Authenticated.`); + createLogEvent(socket, "DEBUG", `Registering RedisIO Socket Events.`); // Register room and broadcasting events registerRoomAndBroadcastEvents(socket);