const path = require("path"); require("dotenv").config({ path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const { io, setSessionData, clearSessionData, getMultipleSessionData, addItemToEndOfList, pubClient } = require("../../server"); const { admin } = require("../firebase/firebase-handler"); const logger = require("../utils/logger"); const { default: CdkJobExport, CdkSelectedCustomer } = require("../cdk/cdk-job-export"); const CdkGetMakes = require("../cdk/cdk-get-makes").default; const CdkCalculateAllocations = require("../cdk/cdk-calculate-allocations").default; const { default: PbsExportJob, PbsSelectedCustomer } = require("../accounting/pbs/pbs-job-export"); const { PbsCalculateAllocationsAp, PbsExportAp } = require("../accounting/pbs/pbs-ap-allocations"); // Middleware for verifying tokens via Firebase authentication function socketAuthMiddleware(socket, next) { const token = socket.handshake.auth.token; if (!token) return next(new Error("Authentication error - no authorization token.")); admin .auth() .verifyIdToken(token) .then((user) => { socket.user = user; next(); }) .catch((error) => { next(new Error("Authentication error", JSON.stringify(error))); }); } // Register all socket events for a given socket connection async function registerSocketEvents(socket) { await setSessionData(socket.id, "log_level", "TRACE"); await createLogEvent(socket, "DEBUG", `Connected and Authenticated.`); // Register CDK-related socket events registerCdkEvents(socket); // Register PBS AR-related socket events registerPbsArEvents(socket); // Register PBS AP-related socket events registerPbsApEvents(socket); // Register room and broadcasting events registerRoomAndBroadcastEvents(socket); // Register event to clear DMS session registerDmsClearSessionEvent(socket); // Register Production Board events registerProductionBoardEvents(socket); // Handle socket disconnection socket.on("disconnect", async () => { await createLogEvent(socket, "DEBUG", `User disconnected.`); }); } // CDK-specific socket events function registerCdkEvents(socket) { socket.on("cdk-export-job", (jobid) => CdkJobExport(socket, jobid)); socket.on("cdk-selected-customer", async (selectedCustomerId) => { await createLogEvent(socket, "DEBUG", `User selected customer ID ${selectedCustomerId}`); CdkSelectedCustomer(socket, selectedCustomerId).catch((err) => console.error(`Error in cdk-selected-customer: ${err}`) ); await setSessionData(socket.id, "selectedCustomer", selectedCustomerId); }); socket.on("cdk-get-makes", async (cdk_dealerid, callback) => { try { const makes = await CdkGetMakes(socket, cdk_dealerid); callback(makes); } catch (error) { await createLogEvent(socket, "ERROR", `Error in cdk-get-makes WS call. ${JSON.stringify(error)}`); } }); socket.on("cdk-calculate-allocations", async (jobid, callback) => { const allocations = await CdkCalculateAllocations(socket, jobid); await createLogEvent(socket, "DEBUG", `Allocations calculated.`); await createLogEvent(socket, "TRACE", `Allocations details: ${JSON.stringify(allocations)}`); callback(allocations); await setSessionData(socket.id, "cdk_allocations", allocations); }); } // PBS AR-specific socket events function registerPbsArEvents(socket) { socket.on("pbs-calculate-allocations", async (jobid, callback) => { const allocations = await CdkCalculateAllocations(socket, jobid); await createLogEvent(socket, "DEBUG", `PBS AR allocations calculated.`); await createLogEvent(socket, "TRACE", `Allocations details: ${JSON.stringify(allocations)}`); callback(allocations); await setSessionData(socket.id, "pbs_allocations", allocations); }); socket.on("pbs-export-job", (jobid) => PbsExportJob(socket, jobid)); socket.on("pbs-selected-customer", async (selectedCustomerId) => { await createLogEvent(socket, "DEBUG", `PBS AR selected customer ID ${selectedCustomerId}`); PbsSelectedCustomer(socket, selectedCustomerId).catch((err) => console.error(`Error in pbs-selected-customer: ${err}`) ); await setSessionData(socket.id, "selectedCustomer", selectedCustomerId); }); } function registerProductionBoardEvents(socket) {} // PBS AP-specific socket events function registerPbsApEvents(socket) { socket.on("pbs-calculate-allocations-ap", async (billids, callback) => { const allocations = await PbsCalculateAllocationsAp(socket, billids); await createLogEvent(socket, "DEBUG", `PBS AP allocations calculated.`); await createLogEvent(socket, "TRACE", `Allocations details: ${JSON.stringify(allocations)}`); callback(allocations); await setSessionData(socket.id, "pbs_ap_allocations", allocations); }); socket.on("pbs-export-ap", async ({ billids, txEnvelope }) => { await setSessionData(socket.id, "pbs_txEnvelope", txEnvelope); PbsExportAp(socket, { billids, txEnvelope }).catch((err) => console.error(`Error in pbs-export-ap: ${err}`)); }); } const getRedisKeyForSocket = (socketId) => `socket:${socketId}:rooms`; // Room management and broadcasting events function registerRoomAndBroadcastEvents(socket) { // Rejoin rooms on reconnect pubClient.lRange(getRedisKeyForSocket(socket.id), 0, -1, (err, rooms) => { if (rooms && rooms.length > 0) { rooms.forEach((room) => { socket.join(room); createLogEvent(socket, "DEBUG", `Client rejoined bodyshop room: ${room}`); }); } }); socket.on("join-bodyshop-room", async (bodyshopUUID) => { socket.join(bodyshopUUID); // Store room in Redis pubClient.rPush(getRedisKeyForSocket(socket.id), bodyshopUUID); await createLogEvent(socket, "DEBUG", `Client joined bodyshop room: ${bodyshopUUID}`); }); socket.on("leave-bodyshop-room", async (bodyshopUUID) => { socket.leave(bodyshopUUID); // Remove room from Redis pubClient.lRem(getRedisKeyForSocket(socket.id), 0, bodyshopUUID); await createLogEvent(socket, "DEBUG", `Client left bodyshop room: ${bodyshopUUID}`); }); socket.on("broadcast-to-bodyshop", async (bodyshopUUID, message) => { io.to(bodyshopUUID).emit("bodyshop-message", message); await createLogEvent(socket, "INFO", `Broadcast message to bodyshop ${bodyshopUUID}`); }); socket.on("disconnect", () => { // Optional: Cleanup Redis entry on disconnect if needed createLogEvent(socket, "DEBUG", `Client disconnected: ${socket.id}`); }); } // DMS session clearing event function registerDmsClearSessionEvent(socket) { socket.on("clear-dms-session", async () => { await clearSessionData(socket.id); // Clear all session data in Redis await createLogEvent(socket, "INFO", `DMS session data cleared for socket ${socket.id}`); }); } // Logging helper functions async function createLogEvent(socket, level, message) { const { logLevel, recordid } = await getMultipleSessionData(socket.id, ["log_level", "recordid"]); if (LogLevelHierarchy(logLevel) >= LogLevelHierarchy(level)) { const logMessage = { timestamp: new Date(), level, message }; // Log the message to the console and emit it via the socket console.log(`[WS LOG EVENT] ${level} - ${logMessage.timestamp} - ${socket.user.email} - ${socket.id} - ${message}`); socket.emit("log-event", logMessage); // Log the event via the logger logger.log("ws-log-event", level, socket.user.email, recordid, { wsmessage: message }); // Add the log message to the Redis list using the helper function await addItemToEndOfList(socket.id, "logEvents", logMessage); } } async function createJsonEvent(socket, level, message, json) { const { logLevel, recordid } = await getMultipleSessionData(socket.id, ["log_level", "recordid"]); if (LogLevelHierarchy(logLevel) >= LogLevelHierarchy(level)) { const logMessage = { timestamp: new Date(), level, message }; // Emit the log message via the socket socket.emit("log-event", logMessage); // Log the JSON event via the logger logger.log("ws-log-event-json", level, socket.user.email, recordid, { wsmessage: message, json }); // Use the helper function to append the log event to the Redis list await addItemToEndOfList(socket.id, "logEvents", logMessage); } } async function createXmlEvent(socket, xml, message, isError = false) { const { logLevel, recordid } = await getMultipleSessionData(socket.id, ["log_level", "recordid"]); if (LogLevelHierarchy(logLevel) >= LogLevelHierarchy("TRACE")) { const logMessage = { timestamp: new Date(), level: isError ? "ERROR" : "TRACE", message: `${message}: ${xml}` }; // Emit the log message via the socket socket.emit("log-event", logMessage); // Log the XML event via the logger logger.log( isError ? "ws-log-event-xml-error" : "ws-log-event-xml", isError ? "ERROR" : "TRACE", socket.user.email, recordid, { wsmessage: message, xml } ); // Use the helper function to append the log event to the Redis list await addItemToEndOfList(socket.id, "logEvents", { ...logMessage, xml }); } } // Log level hierarchy function LogLevelHierarchy(level) { const levels = { XML: 5, TRACE: 5, DEBUG: 4, INFO: 3, WARNING: 2, ERROR: 1 }; return levels[level] || 3; } // Socket.IO Middleware and Connection io.use(socketAuthMiddleware); io.on("connection", registerSocketEvents); // Export logging helpers exports.createLogEvent = createLogEvent; exports.createXmlEvent = createXmlEvent; exports.createJsonEvent = createJsonEvent;