Compare commits

..

5 Commits

Author SHA1 Message Date
Dave Richer
699ffc822a feature/IO-2967-Better-Refetch-Handling - Remove unused include
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-27 15:07:35 -04:00
Dave Richer
4e35f5402c feature/IO-2967-Better-Refetch-Handling - Add note to server.js
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-27 15:06:52 -04:00
Dave Richer
9b997d0924 feature/IO-2967-Better-Refetch-Handling - match broadcast room name to match redis naming convention
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-27 15:00:25 -04:00
Dave Richer
d705f8211e feature/IO-2967-Better-Refetch-Handling - Bug fixes and hardening
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-27 14:52:38 -04:00
Dave Richer
03761bbb2a feature/IO-2967-Better-Refetch-Handling - Implementation
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-27 13:32:59 -04:00
8 changed files with 188 additions and 98 deletions

View File

@@ -23,6 +23,9 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionTyp
const fired = useRef(false); const fired = useRef(false);
const client = useApolloClient(); const client = useApolloClient();
const { socket } = useContext(SocketContext); // Get the socket from context 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 { const {
treatments: { Websocket_Production } treatments: { Websocket_Production }
@@ -126,19 +129,44 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionTyp
} }
}; };
const handleReconnect = () => { const handleDisconnect = () => {
//If we were disconnected from the board, we missed stuff. We need to refresh it entirely. // Capture the disconnection time
if (refetch) refetch(); 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("production-job-updated", handleJobUpdates);
socket.on("reconnect", handleReconnect); socket.on("disconnect", handleDisconnect);
socket.on("connect", handleReconnect);
// Clean up on unmount or when dependencies change // Clean up on unmount or when dependencies change
return () => { return () => {
socket.off("production-job-updated", handleJobUpdates); 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(() => { const filteredAssociationSettings = useMemo(() => {
return associationSettings?.associations[0] || null; return associationSettings?.associations[0] || null;

View File

@@ -1,5 +1,5 @@
import { useApolloClient, useQuery, useSubscription } from "@apollo/client"; import { useApolloClient, useQuery, useSubscription } from "@apollo/client";
import React, { useContext, useEffect, useState } from "react"; import React, { useContext, useEffect, useState, useRef } from "react";
import { import {
QUERY_EXACT_JOB_IN_PRODUCTION, QUERY_EXACT_JOB_IN_PRODUCTION,
QUERY_EXACT_JOBS_IN_PRODUCTION, QUERY_EXACT_JOBS_IN_PRODUCTION,
@@ -16,6 +16,10 @@ export default function ProductionListTableContainer({ bodyshop, subscriptionTyp
const client = useApolloClient(); const client = useApolloClient();
const { socket } = useContext(SocketContext); const { socket } = useContext(SocketContext);
const [joblist, setJoblist] = useState([]); 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 // Get Split treatment
const { 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. const handleDisconnect = () => {
if (refetch) refetch(); // 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("production-job-updated", handleJobUpdates);
socket.on("reconnect", handleReconnect); socket.on("disconnect", handleDisconnect);
socket.on("connect", handleReconnect);
// Clean up on unmount or when dependencies change // Clean up on unmount or when dependencies change
return () => { return () => {
socket.off("production-job-updated", handleJobUpdates); 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]); }, [subscriptionEnabled, socket, bodyshop, client, refetch]);
@@ -151,6 +184,7 @@ export default function ProductionListTableContainer({ bodyshop, subscriptionTyp
fetchPolicy: "network-only" fetchPolicy: "network-only"
}); });
}; };
const getUpdatedJobsData = (jobIds) => { const getUpdatedJobsData = (jobIds) => {
client.query({ client.query({
query: QUERY_EXACT_JOBS_IN_PRODUCTION, query: QUERY_EXACT_JOBS_IN_PRODUCTION,

View File

@@ -10,7 +10,7 @@ const { createClient } = require("redis");
const { createAdapter } = require("@socket.io/redis-adapter"); const { createAdapter } = require("@socket.io/redis-adapter");
const logger = require("./server/utils/logger"); const logger = require("./server/utils/logger");
const { redisSocketEvents } = require("./server/web-sockets/redisSocketEvents"); const { redisSocketEvents } = require("./server/web-sockets/redisSocketEvents");
const { instrument, RedisStore } = require("@socket.io/admin-ui"); const { instrument } = require("@socket.io/admin-ui");
const { isString, isEmpty } = require("lodash"); const { isString, isEmpty } = require("lodash");
const applyRedisHelpers = require("./server/utils/redisHelpers"); const applyRedisHelpers = require("./server/utils/redisHelpers");
@@ -192,7 +192,7 @@ const main = async () => {
const server = http.createServer(app); const server = http.createServer(app);
const { pubClient, ioRedis } = await applySocketIO(server, app); const { pubClient, ioRedis } = await applySocketIO(server, app);
const api = applyRedisHelpers(pubClient, app); const api = applyRedisHelpers(pubClient, app, logger);
const ioHelpers = applyIOHelpers(app, api, ioRedis, logger); const ioHelpers = applyIOHelpers(app, api, ioRedis, logger);
// Legacy Socket Events // Legacy Socket Events
@@ -211,11 +211,11 @@ const main = async () => {
}; };
// Start server // Start server
try { main().catch((error) => {
main();
} catch (error) {
logger.log(`Main-API-Error: Something was not caught in the application.`, "error", "api", null, { logger.log(`Main-API-Error: Something was not caught in the application.`, "error", "api", null, {
error: error.message, error: error.message,
errorjson: JSON.stringify(error) errorjson: JSON.stringify(error)
}); });
} // Note: If we want the app to crash on all uncaught async operations, we would
// need to put a `process.exit(1);` here
});

View File

@@ -10,7 +10,7 @@ const logger = require("../utils/logger");
const client = require("../graphql-client/graphql-client").client; const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries"); const queries = require("../graphql-client/queries");
const generateEmailTemplate = require("./generateTemplate"); const generateEmailTemplate = require("./generateTemplate");
const moment = require("moment-timezone"); const moment = require("moment");
const { taskEmailQueue } = require("./tasksEmailsQueue"); const { taskEmailQueue } = require("./tasksEmailsQueue");
const ses = new aws.SES({ const ses = new aws.SES({
@@ -108,13 +108,12 @@ const getEndpoints = (bodyshop) =>
: "https://romeonline.io" : "https://romeonline.io"
}); });
const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId, dateLine) => { const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId) => {
const endPoints = getEndpoints(bodyshop); const endPoints = getEndpoints(bodyshop);
return { return {
header: title, header: title,
subHeader: `Body Shop: ${bodyshop.shopname} | Priority: ${formatPriority(priority)} ${formatDate(dueDate)}`, subHeader: `Body Shop: ${bodyshop.shopname} | Priority: ${formatPriority(priority)} ${formatDate(dueDate)}`,
body: `Reference: ${job.ro_number || "N/A"} | ${job.ownr_co_nm ? job.ownr_co_nm : `${job.ownr_fn || ""} ${job.ownr_ln || ""}`.trim()} | ${`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`.trim()}<br>${description ? description.concat("<br>") : ""}<a href="${endPoints}/manage/tasks/alltasks?taskid=${taskId}">View this task.</a>`, body: `Reference: ${job.ro_number || "N/A"} | ${job.ownr_co_nm ? job.ownr_co_nm : `${job.ownr_fn || ""} ${job.ownr_ln || ""}`.trim()} | ${`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`.trim()}<br>${description ? description.concat("<br>") : ""}<a href="${endPoints}/manage/tasks/alltasks?taskid=${taskId}">View this task.</a>`
dateLine
}; };
}; };
@@ -179,8 +178,6 @@ const taskAssignedEmail = async (req, res) => {
id: newTask.id id: newTask.id
}); });
const dateLine = moment().tz(tasks_by_pk.bodyshop.timezone).format("M/DD/YYYY @ hh:mm a");
sendMail( sendMail(
"assigned", "assigned",
tasks_by_pk.assigned_to_employee.user_email, tasks_by_pk.assigned_to_employee.user_email,
@@ -193,8 +190,7 @@ const taskAssignedEmail = async (req, res) => {
newTask.due_date, newTask.due_date,
tasks_by_pk.bodyshop, tasks_by_pk.bodyshop,
tasks_by_pk.job, tasks_by_pk.job,
newTask.id, newTask.id
dateLine
) )
), ),
null, null,
@@ -251,7 +247,7 @@ const tasksRemindEmail = async (req, res) => {
const fromEmails = InstanceManager({ const fromEmails = InstanceManager({
imex: "ImEX Online <noreply@imex.online>", imex: "ImEX Online <noreply@imex.online>",
rome: rome:
tasksRequest?.tasks[0].bodyshop.convenient_company === "promanager" tasksRequest?.tasks[0].bodyshop.convenient_company === "promanager"
? "ProManager <noreply@promanager.web-est.com>" ? "ProManager <noreply@promanager.web-est.com>"
: "Rome Online <noreply@romeonline.io>" : "Rome Online <noreply@romeonline.io>"
}); });
@@ -263,8 +259,6 @@ const tasksRemindEmail = async (req, res) => {
const taskIds = groupedTasks[recipient.email].map((task) => task.id); const taskIds = groupedTasks[recipient.email].map((task) => task.id);
const dateLine = moment().tz(tasksRequest?.tasks[0].bodyshop.timezone).format("M/DD/YYYY @ hh:mm a");
// There is only the one email to send to this author. // There is only the one email to send to this author.
if (recipient.count === 1) { if (recipient.count === 1) {
const onlyTask = groupedTasks[recipient.email][0]; const onlyTask = groupedTasks[recipient.email][0];
@@ -280,8 +274,7 @@ const tasksRemindEmail = async (req, res) => {
onlyTask.due_date, onlyTask.due_date,
onlyTask.bodyshop, onlyTask.bodyshop,
onlyTask.job, onlyTask.job,
onlyTask.id, onlyTask.id
dateLine
) )
); );
} }
@@ -290,7 +283,7 @@ const tasksRemindEmail = async (req, res) => {
const endPoints = InstanceManager({ const endPoints = InstanceManager({
imex: process.env?.NODE_ENV === "test" ? "https://test.imex.online" : "https://imex.online", imex: process.env?.NODE_ENV === "test" ? "https://test.imex.online" : "https://imex.online",
rome: rome:
tasksRequest?.tasks[0].bodyshop.convenient_company === "promanager" tasksRequest?.tasks[0].bodyshop.convenient_company === "promanager"
? process.env?.NODE_ENV === "test" ? process.env?.NODE_ENV === "test"
? "https//test.promanager.web-est.com" ? "https//test.promanager.web-est.com"
: "https://promanager.web-est.com" : "https://promanager.web-est.com"
@@ -304,7 +297,6 @@ const tasksRemindEmail = async (req, res) => {
emailData.html = generateEmailTemplate({ emailData.html = generateEmailTemplate({
header: `${allTasks.length} Tasks require your attention`, header: `${allTasks.length} Tasks require your attention`,
subHeader: `Please click on the Tasks below to view the Task.`, subHeader: `Please click on the Tasks below to view the Task.`,
dateLine,
body: `<ul> body: `<ul>
${allTasks ${allTasks
.map((task) => .map((task) =>

View File

@@ -2489,7 +2489,6 @@ exports.QUERY_REMIND_TASKS = `
bodyshop { bodyshop {
shopname shopname
convenient_company convenient_company
timezone
} }
bodyshopid bodyshopid
} }
@@ -2513,7 +2512,6 @@ query QUERY_TASK_BY_ID($id: uuid!) {
bodyshop{ bodyshop{
shopname shopname
convenient_company convenient_company
timezone
} }
job{ job{
ro_number ro_number

View File

@@ -1,5 +1,5 @@
const applyIOHelpers = (app, api, io, logger) => { const applyIOHelpers = (app, api, io, logger) => {
const getBodyshopRoom = (bodyshopID) => `broadcast-room-${bodyshopID}`; const getBodyshopRoom = (bodyshopID) => `bodyshop-broadcast-room:${bodyshopID}`;
const ioHelpersAPI = { const ioHelpersAPI = {
getBodyshopRoom getBodyshopRoom

View File

@@ -1,48 +1,71 @@
const logger = require("./logger");
/** /**
* Apply Redis helper functions * Apply Redis helper functions
* @param pubClient * @param pubClient
* @param app * @param app
*/ */
const applyRedisHelpers = (pubClient, app) => { const applyRedisHelpers = (pubClient, app, logger) => {
// Store session data in Redis // Store session data in Redis
const setSessionData = async (socketId, key, value) => { const setSessionData = async (socketId, key, value) => {
await pubClient.hSet(`socket:${socketId}`, key, JSON.stringify(value)); // Use Redis pubClient try {
await pubClient.hSet(`socket:${socketId}`, key, JSON.stringify(value)); // Use Redis pubClient
} catch (error) {
logger.log(`Error Setting Session Data for socket ${socketId}: ${error}`, "ERROR", "redis");
}
}; };
// Retrieve session data from Redis // Retrieve session data from Redis
const getSessionData = async (socketId, key) => { const getSessionData = async (socketId, key) => {
const data = await pubClient.hGet(`socket:${socketId}`, key); try {
return data ? JSON.parse(data) : null; const data = await pubClient.hGet(`socket:${socketId}`, key);
return data ? JSON.parse(data) : null;
} catch (error) {
logger.log(`Error Getting Session Data for socket ${socketId}: ${error}`, "ERROR", "redis");
}
}; };
// Clear session data from Redis // Clear session data from Redis
const clearSessionData = async (socketId) => { const clearSessionData = async (socketId) => {
await pubClient.del(`socket:${socketId}`); try {
await pubClient.del(`socket:${socketId}`);
} catch (error) {
logger.log(`Error Clearing Session Data for socket ${socketId}: ${error}`, "ERROR", "redis");
}
}; };
// Store multiple session data in Redis // Store multiple session data in Redis
const setMultipleSessionData = async (socketId, keyValues) => { const setMultipleSessionData = async (socketId, keyValues) => {
// keyValues is expected to be an object { key1: value1, key2: value2, ... } try {
const entries = Object.entries(keyValues).map(([key, value]) => [key, JSON.stringify(value)]); // keyValues is expected to be an object { key1: value1, key2: value2, ... }
await pubClient.hSet(`socket:${socketId}`, ...entries.flat()); const entries = Object.entries(keyValues).map(([key, value]) => [key, JSON.stringify(value)]);
await pubClient.hSet(`socket:${socketId}`, ...entries.flat());
} catch (error) {
logger.log(`Error Setting Multiple Session Data for socket ${socketId}: ${error}`, "ERROR", "redis");
}
}; };
// Retrieve multiple session data from Redis // Retrieve multiple session data from Redis
const getMultipleSessionData = async (socketId, keys) => { const getMultipleSessionData = async (socketId, keys) => {
const data = await pubClient.hmGet(`socket:${socketId}`, keys); try {
// Redis returns an object with null values for missing keys, so we parse the non-null ones const data = await pubClient.hmGet(`socket:${socketId}`, keys);
return Object.fromEntries(keys.map((key, index) => [key, data[index] ? JSON.parse(data[index]) : null])); // Redis returns an object with null values for missing keys, so we parse the non-null ones
return Object.fromEntries(keys.map((key, index) => [key, data[index] ? JSON.parse(data[index]) : null]));
} catch (error) {
logger.log(`Error Getting Multiple Session Data for socket ${socketId}: ${error}`, "ERROR", "redis");
}
}; };
const setMultipleFromArraySessionData = async (socketId, keyValueArray) => { const setMultipleFromArraySessionData = async (socketId, keyValueArray) => {
// Use Redis multi/pipeline to batch the commands try {
const multi = pubClient.multi(); // Use Redis multi/pipeline to batch the commands
const multi = pubClient.multi();
keyValueArray.forEach(([key, value]) => { keyValueArray.forEach(([key, value]) => {
multi.hSet(`socket:${socketId}`, key, JSON.stringify(value)); multi.hSet(`socket:${socketId}`, key, JSON.stringify(value));
}); });
await multi.exec(); // Execute all queued commands
await multi.exec(); // Execute all queued commands } catch (error) {
logger.log(`Error Setting Multiple Session Data for socket ${socketId}: ${error}`, "ERROR", "redis");
}
}; };
// Helper function to add an item to the end of the Redis list // Helper function to add an item to the end of the Redis list
@@ -50,7 +73,7 @@ const applyRedisHelpers = (pubClient, app) => {
try { try {
await pubClient.rPush(`socket:${socketId}:${key}`, JSON.stringify(newItem)); await pubClient.rPush(`socket:${socketId}:${key}`, JSON.stringify(newItem));
} catch (error) { } catch (error) {
console.error(`Error adding item to the end of the list for socket ${socketId}:`, error); logger.log(`Error adding item to the end of the list for socket ${socketId}: ${error}`, "ERROR", "redis");
} }
}; };
@@ -59,7 +82,7 @@ const applyRedisHelpers = (pubClient, app) => {
try { try {
await pubClient.lPush(`socket:${socketId}:${key}`, JSON.stringify(newItem)); await pubClient.lPush(`socket:${socketId}:${key}`, JSON.stringify(newItem));
} catch (error) { } catch (error) {
console.error(`Error adding item to the beginning of the list for socket ${socketId}:`, error); logger.log(`Error adding item to the beginning of the list for socket ${socketId}: ${error}`, "ERROR", "redis");
} }
}; };
@@ -68,33 +91,33 @@ const applyRedisHelpers = (pubClient, app) => {
try { try {
await pubClient.del(`socket:${socketId}:${key}`); await pubClient.del(`socket:${socketId}:${key}`);
} catch (error) { } catch (error) {
console.error(`Error clearing list for socket ${socketId}:`, error); logger.log(`Error clearing list for socket ${socketId}: ${error}`, "ERROR", "redis");
} }
}; };
// Add methods to manage room users // Add methods to manage room users
const addUserToRoom = async (bodyshopUUID, user) => { const addUserToRoom = async (room, user) => {
try { try {
await pubClient.sAdd(`bodyshopRoom:${bodyshopUUID}`, JSON.stringify(user)); await pubClient.sAdd(room, JSON.stringify(user));
} catch (err) { } catch (error) {
console.error(`Error adding user to room: ${bodyshopUUID}`); logger.log(`Error adding user to room ${room}: ${error}`, "ERROR", "redis");
} }
}; };
const removeUserFromRoom = async (bodyshopUUID, user) => { const removeUserFromRoom = async (room, user) => {
try { try {
await pubClient.sRem(`bodyshopRoom:${bodyshopUUID}`, JSON.stringify(user)); await pubClient.sRem(room, JSON.stringify(user));
} catch (err) { } catch (error) {
console.error(`Error remove user from room: ${bodyshopUUID}`); logger.log(`Error removing user to room ${room}: ${error}`, "ERROR", "redis");
} }
}; };
const getUsersInRoom = async (bodyshopUUID) => { const getUsersInRoom = async (room) => {
try { try {
const users = await pubClient.sMembers(`bodyshopRoom:${bodyshopUUID}`); const users = await pubClient.sMembers(room);
return users.map((user) => JSON.parse(user)); return users.map((user) => JSON.parse(user));
} catch (err) { } catch (error) {
console.error(`Error getUsersInRoom: ${bodyshopUUID}`); logger.log(`Error getting users in room ${room}: ${error}`, "ERROR", "redis");
} }
}; };

View File

@@ -30,52 +30,67 @@ const redisSocketEvents = (io, { addUserToRoom, getUsersInRoom, removeUserFromRo
// Room management and broadcasting events // Room management and broadcasting events
function registerRoomAndBroadcastEvents(socket) { function registerRoomAndBroadcastEvents(socket) {
socket.on("join-bodyshop-room", async (bodyshopUUID) => { socket.on("join-bodyshop-room", async (bodyshopUUID) => {
const room = getBodyshopRoom(bodyshopUUID); try {
socket.join(room); const room = getBodyshopRoom(bodyshopUUID);
await addUserToRoom(room, { uid: socket.user.uid, email: socket.user.email }); socket.join(room);
createLogEvent(socket, "DEBUG", `Client joined bodyshop room: ${bodyshopUUID}`); await addUserToRoom(room, { uid: socket.user.uid, email: socket.user.email, socket: socket.id });
createLogEvent(socket, "DEBUG", `Client joined bodyshop room: ${room}`);
// Notify all users in the room about the updated user list // Notify all users in the room about the updated user list
const usersInRoom = await getUsersInRoom(bodyshopUUID); const usersInRoom = await getUsersInRoom(room);
io.to(room).emit("room-users-updated", usersInRoom); io.to(room).emit("room-users-updated", usersInRoom);
} catch (error) {
createLogEvent(socket, "ERROR", `Error joining room: ${error}`);
}
}); });
socket.on("leave-bodyshop-room", async (bodyshopUUID) => { socket.on("leave-bodyshop-room", async (bodyshopUUID) => {
const room = getBodyshopRoom(bodyshopUUID); try {
socket.leave(room); const room = getBodyshopRoom(bodyshopUUID);
createLogEvent(socket, "DEBUG", `Client left bodyshop room: ${room}`); 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) => { socket.on("get-room-users", async (bodyshopUUID, callback) => {
const usersInRoom = await getUsersInRoom(getBodyshopRoom(bodyshopUUID)); try {
callback(usersInRoom); 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) => { socket.on("broadcast-to-bodyshop", async (bodyshopUUID, message) => {
const room = getBodyshopRoom(bodyshopUUID); try {
io.to(room).emit("bodyshop-message", message); const room = getBodyshopRoom(bodyshopUUID);
createLogEvent(socket, "INFO", `Broadcast message to bodyshop ${room}`); 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 () => { socket.on("disconnect", async () => {
createLogEvent(socket, "DEBUG", `User disconnected.`); try {
createLogEvent(socket, "DEBUG", `User disconnected.`);
// Get all rooms the socket is part of // Get all rooms the socket is part of
const rooms = Array.from(socket.rooms).filter((room) => room !== socket.id); const rooms = Array.from(socket.rooms).filter((room) => room !== socket.id);
for (const room of rooms) {
for (const bodyshopRoom of rooms) { await removeUserFromRoom(room, { uid: socket.user.uid, email: socket.user.email, socket: socket.id });
await removeUserFromRoom(bodyshopRoom, { uid: socket.user.uid, email: socket.user.email }); }
} catch (error) {
// Notify all users in the room about the updated user list createLogEvent(socket, "ERROR", `Error getting room: ${error}`);
const usersInRoom = await getUsersInRoom(bodyshopRoom);
io.to(bodyshopRoom).emit("room-users-updated", usersInRoom);
} }
}); });
} }
// Register all socket events for a given socket connection // Register all socket events for a given socket connection
function registerSocketEvents(socket) { function registerSocketEvents(socket) {
createLogEvent(socket, "DEBUG", `Connected and Authenticated.`); createLogEvent(socket, "DEBUG", `Registering RedisIO Socket Events.`);
// Register room and broadcasting events // Register room and broadcasting events
registerRoomAndBroadcastEvents(socket); registerRoomAndBroadcastEvents(socket);